Skip to content
Commits on Source (2)
<?php <?php
/**
* NormalizeEntityUrnsDelegate
* @author edgebal
*/
namespace Minds\Core\Boost\Campaigns\Delegates; namespace Minds\Core\Boost\Campaigns\Delegates;
...@@ -79,9 +75,6 @@ class NormalizeEntityUrnsDelegate ...@@ -79,9 +75,6 @@ class NormalizeEntityUrnsDelegate
} }
} }
$campaign return $campaign->setEntityUrns($entityUrns);
->setEntityUrns($entityUrns);
return $campaign;
} }
} }
<?php <?php
/**
* PaymentsDelegate
* @author edgebal
*/
namespace Minds\Core\Boost\Campaigns\Delegates; namespace Minds\Core\Boost\Campaigns\Delegates;
...@@ -51,8 +47,6 @@ class PaymentsDelegate ...@@ -51,8 +47,6 @@ class PaymentsDelegate
*/ */
public function onCreate(Campaign $campaign, $paymentPayload = null) public function onCreate(Campaign $campaign, $paymentPayload = null)
{ {
// NOTE: Do not spec test. Individually test the other methods.
$this->validateBudget($campaign); $this->validateBudget($campaign);
if (!$paymentPayload) { if (!$paymentPayload) {
...@@ -60,12 +54,9 @@ class PaymentsDelegate ...@@ -60,12 +54,9 @@ class PaymentsDelegate
} }
$this->pay($campaign, $paymentPayload); $this->pay($campaign, $paymentPayload);
$this->validatePayments($campaign); $this->validatePayments($campaign);
$campaign = $this->updateImpressionsByCpm($campaign); return $this->updateImpressionsByCpm($campaign);
return $campaign;
} }
/** /**
...@@ -77,11 +68,7 @@ class PaymentsDelegate ...@@ -77,11 +68,7 @@ class PaymentsDelegate
*/ */
public function onUpdate(Campaign $campaign, Campaign $campaignRef, $paymentPayload = null) public function onUpdate(Campaign $campaign, Campaign $campaignRef, $paymentPayload = null)
{ {
// NOTE: Do not spec test. Individually test the other methods. $campaignRef->setBudgetType($campaign->getBudgetType());
$campaignRef
->setBudgetType($campaign->getBudgetType());
$this->validateBudget($campaignRef); $this->validateBudget($campaignRef);
if ($paymentPayload) { if ($paymentPayload) {
...@@ -90,9 +77,7 @@ class PaymentsDelegate ...@@ -90,9 +77,7 @@ class PaymentsDelegate
$this->validatePayments($campaignRef); $this->validatePayments($campaignRef);
} }
$campaign = $this->updateImpressionsByCpm($campaign); return $this->updateImpressionsByCpm($campaign);
return $campaign;
} }
/** /**
...@@ -102,8 +87,6 @@ class PaymentsDelegate ...@@ -102,8 +87,6 @@ class PaymentsDelegate
*/ */
public function onStateChange(Campaign $campaign) public function onStateChange(Campaign $campaign)
{ {
// NOTE: Do not spec test. Individually test the other methods.
$isFinished = in_array($campaign->getDeliveryStatus(), [ $isFinished = in_array($campaign->getDeliveryStatus(), [
Campaign::STATUS_REJECTED, Campaign::STATUS_REJECTED,
Campaign::STATUS_REVOKED, Campaign::STATUS_REVOKED,
...@@ -195,49 +178,29 @@ class PaymentsDelegate ...@@ -195,49 +178,29 @@ class PaymentsDelegate
public function refund(Campaign $campaign) public function refund(Campaign $campaign)
{ {
$latestPaymentSource = ''; $latestPaymentSource = '';
// Sum up all payments
$paid = 0; $paid = 0;
$refundThreshold = 0.1;
foreach ($campaign->getPayments() as $payment) { foreach ($campaign->getPayments() as $payment) {
// Sum!
$paid += $payment->getAmount(); $paid += $payment->getAmount();
if ($payment->getAmount() > 0 && $payment->getSource()) { if ($payment->getAmount() > 0 && $payment->getSource()) {
// Grab the latest wallet used by campaign's owner
$latestPaymentSource = $payment->getSource(); $latestPaymentSource = $payment->getSource();
} }
} }
// If amount is < 0.1 (minimum fraction), don't refund if ($paid <= $refundThreshold) {
if ($paid <= 0.1) {
return $campaign; return $campaign;
} }
// Grab a fresh count of impressions met $impressionsMet = $this->metrics->setCampaign($campaign)->getImpressionsMet();
$impressionsMet = $this->metrics
->setCampaign($campaign)
->getImpressionsMet();
// Calculate the cost of the impressions met
$cost = ($impressionsMet / 1000) * $campaign->cpm(); $cost = ($impressionsMet / 1000) * $campaign->cpm();
// Calculate the amount to be refunded
$amount = $paid - $cost; $amount = $paid - $cost;
// If amount is < 0.1 (minimum fraction), don't refund if ($amount < $refundThreshold) {
if ($amount < 0.1) {
return $campaign; return $campaign;
} }
// Execute refund
switch ($campaign->getBudgetType()) { switch ($campaign->getBudgetType()) {
case 'tokens': case 'tokens':
$payment = new Payment(); $payment = new Payment();
...@@ -249,8 +212,7 @@ class PaymentsDelegate ...@@ -249,8 +212,7 @@ class PaymentsDelegate
->setTimeCreated(time()); ->setTimeCreated(time());
try { try {
$this->onchainPayments $this->onchainPayments->refund($payment);
->refund($payment);
} catch (Exception $e) { } catch (Exception $e) {
throw new CampaignException("Error registering refund: {$e->getMessage()}"); throw new CampaignException("Error registering refund: {$e->getMessage()}");
} }
......
...@@ -2,14 +2,71 @@ ...@@ -2,14 +2,71 @@
namespace Spec\Minds\Core\Boost\Campaigns\Delegates; namespace Spec\Minds\Core\Boost\Campaigns\Delegates;
use Minds\Core\Boost\Campaigns\Campaign;
use Minds\Core\Boost\Campaigns\CampaignException;
use Minds\Core\Boost\Campaigns\Delegates\NormalizeEntityUrnsDelegate; use Minds\Core\Boost\Campaigns\Delegates\NormalizeEntityUrnsDelegate;
use Minds\Core\EntitiesBuilder;
use Minds\Core\Security\ACL;
use Minds\Entities\Activity;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior; use PhpSpec\ObjectBehavior;
use Prophecy\Argument; use Prophecy\Argument;
class NormalizeEntityUrnsDelegateSpec extends ObjectBehavior class NormalizeEntityUrnsDelegateSpec extends ObjectBehavior
{ {
protected $acl;
protected $entitiesBuilder;
public function let(ACL $acl, EntitiesBuilder $entitiesBuilder)
{
$this->beConstructedWith($acl, $entitiesBuilder);
$this->acl = $acl;
$this->entitiesBuilder = $entitiesBuilder;
}
public function it_is_initializable() public function it_is_initializable()
{ {
$this->shouldHaveType(NormalizeEntityUrnsDelegate::class); $this->shouldHaveType(NormalizeEntityUrnsDelegate::class);
} }
public function it_should_throw_an_exception_if_campaign_has_no_entities_on_create(Campaign $campaign)
{
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getEntityUrns()->shouldBeCalled()->willReturn([]);
$this->shouldThrow(CampaignException::class)->duringOnCreate($campaign);
}
public function it_should_throw_an_exception_if_entity_urn_is_invalid_on_create(Campaign $campaign)
{
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getEntityUrns()->shouldBeCalled()->willReturn(['invalid1', 'invalid2']);
$this->shouldThrow(CampaignException::class)->duringOnCreate($campaign);
}
public function it_should_throw_an_exception_if_entity_urn_does_not_exist_on_create(Campaign $campaign)
{
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getEntityUrns()->shouldBeCalled()->willReturn(['urn:activity:12345']);
$this->entitiesBuilder->single(12345)->shouldBeCalled()->willReturn(false);
$this->shouldThrow(CampaignException::class)->duringOnCreate($campaign);
}
public function it_should_throw_an_exception_if_entity_urn_is_not_readable_on_create(Campaign $campaign, Activity $activity)
{
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getEntityUrns()->shouldBeCalled()->willReturn(['urn:activity:12345']);
$this->entitiesBuilder->single(12345)->shouldBeCalled()->willReturn($activity);
$this->acl->read($activity, Argument::type(User::class))->shouldBeCalled()->willReturn(false);
$this->shouldThrow(CampaignException::class)->duringOnCreate($campaign);
}
public function it_should_normalise_entity_urns_on_create(Campaign $campaign, Activity $activity)
{
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getEntityUrns()->shouldBeCalled()->willReturn(['urn:activity:12345']);
$this->entitiesBuilder->single(12345)->shouldBeCalled()->willReturn($activity);
$this->acl->read($activity, Argument::type(User::class))->shouldBeCalled()->willReturn(true);
$campaign->setEntityUrns(["urn:activity:12345"])->shouldBeCalled()->willReturn($campaign);
$this->onCreate($campaign);
}
} }
...@@ -2,14 +2,153 @@ ...@@ -2,14 +2,153 @@
namespace Spec\Minds\Core\Boost\Campaigns\Delegates; namespace Spec\Minds\Core\Boost\Campaigns\Delegates;
use Minds\Core\Boost\Campaigns\Campaign;
use Minds\Core\Boost\Campaigns\CampaignException;
use Minds\Core\Boost\Campaigns\Delegates\PaymentsDelegate; use Minds\Core\Boost\Campaigns\Delegates\PaymentsDelegate;
use Minds\Core\Boost\Campaigns\Metrics;
use Minds\Core\Boost\Campaigns\Payments\Onchain;
use Minds\Core\Boost\Campaigns\Payments\Payment;
use Minds\Core\Config;
use PhpSpec\ObjectBehavior; use PhpSpec\ObjectBehavior;
use Prophecy\Argument; use Prophecy\Argument;
class PaymentsDelegateSpec extends ObjectBehavior class PaymentsDelegateSpec extends ObjectBehavior
{ {
/** @var Config */
protected $config;
/** @var Onchain */
protected $onchainPayments;
/** @var Metrics */
protected $metrics;
public function let(Config $config, Onchain $onchainPayments, Metrics $metrics)
{
$this->beConstructedWith($config, $onchainPayments, $metrics);
$this->config = $config;
$this->onchainPayments = $onchainPayments;
$this->metrics = $metrics;
}
public function it_is_initializable() public function it_is_initializable()
{ {
$this->shouldHaveType(PaymentsDelegate::class); $this->shouldHaveType(PaymentsDelegate::class);
} }
public function it_should_throw_an_exception_if_there_is_no_budget(Campaign $campaign)
{
$campaign->getBudget()->shouldBeCalled()->willReturn(0);
$this->shouldThrow(CampaignException::class)->duringValidateBudget($campaign);
}
public function it_should_throw_an_exception_if_budget_type_is_invalid(Campaign $campaign)
{
$campaign->getBudget()->shouldBeCalled()->willReturn(2);
$campaign->getBudgetType()->shouldBeCalled()->willReturn(null);
$this->shouldThrow(CampaignException::class)->duringValidateBudget($campaign);
}
public function it_should_validate_budget(Campaign $campaign)
{
$campaign->getBudget()->shouldBeCalled()->willReturn(2);
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$this->validateBudget($campaign);
}
public function it_should_validate_payments(Campaign $campaign)
{
$this->validatePayments($campaign);
}
public function it_should_throw_an_exception_on_invalid_payment_signature(Campaign $campaign)
{
$payload = [];
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$this->shouldThrow(CampaignException::class)->duringPay($campaign, $payload);
}
public function it_should_throw_an_exception_if_error_registering_payment(Campaign $campaign)
{
$payload = [
'txHash' => 'abc123',
'address' => '0x1234',
'amount' => '1'
];
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getGuid()->shouldBeCalled()->willReturn(23456);
$this->onchainPayments->register(Argument::type(Payment::class))->willThrow(\Exception::class);
$this->shouldThrow(CampaignException::class)->duringPay($campaign, $payload);
}
public function it_should_register_a_payment(Campaign $campaign)
{
$payload = [
'txHash' => 'abc123',
'address' => '0x1234',
'amount' => '1'
];
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getGuid()->shouldBeCalled()->willReturn(23456);
$this->onchainPayments->register(Argument::type(Payment::class))->shouldBeCalled();
$campaign->pushPayment(Argument::type(Payment::class))->shouldBeCalled();
$this->pay($campaign, $payload);
}
public function it_should_not_refund_if_below_refund_threshold(Campaign $campaign, Payment $payment1)
{
$payments = [
$payment1
];
$this->onchainPayments->refund(Argument::type(Payment::class))->shouldNotBeCalled();
$campaign->getPayments()->shouldBeCalled()->willReturn($payments);
$payment1->getAmount()->shouldBeCalled()->willReturn(0.01);
$payment1->getSource()->shouldBeCalled()->willReturn('something');
$this->refund($campaign);
}
public function it_should_throw_an_exception_if_refund_fails(Campaign $campaign, Payment $payment1)
{
$payments = [
$payment1
];
$campaign->getPayments()->shouldBeCalled()->willReturn($payments);
$payment1->getAmount()->shouldBeCalled()->willReturn(2);
$payment1->getSource()->shouldBeCalled()->willReturn('something');
$this->metrics->setCampaign($campaign)->shouldBeCalled()->willReturn($this->metrics);
$this->metrics->getImpressionsMet()->shouldBeCalled()->willReturn(500);
$campaign->cpm()->shouldBeCalled()->willReturn(1);
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getGuid()->shouldBeCalled()->willReturn(23456);
$this->onchainPayments->refund(Argument::type(Payment::class))->shouldBeCalled()->willThrow(\Exception::class);
$this->shouldThrow(CampaignException::class)->duringRefund($campaign);
}
public function it_should_register_a_refund(Campaign $campaign, Payment $payment1)
{
$payments = [
$payment1
];
$campaign->getPayments()->shouldBeCalled()->willReturn($payments);
$payment1->getAmount()->shouldBeCalled()->willReturn(2);
$payment1->getSource()->shouldBeCalled()->willReturn('something');
$this->metrics->setCampaign($campaign)->shouldBeCalled()->willReturn($this->metrics);
$this->metrics->getImpressionsMet()->shouldBeCalled()->willReturn(500);
$campaign->cpm()->shouldBeCalled()->willReturn(1);
$campaign->getBudgetType()->shouldBeCalled()->willReturn('tokens');
$campaign->getOwnerGuid()->shouldBeCalled()->willReturn(12345);
$campaign->getGuid()->shouldBeCalled()->willReturn(23456);
$this->onchainPayments->refund(Argument::type(Payment::class))->shouldBeCalled();
$campaign->pushPayment(Argument::type(Payment::class))->shouldBeCalled();
$this->refund($campaign);
}
} }