...
 
<?php
/**
* NormalizeEntityUrnsDelegate
* @author edgebal
*/
namespace Minds\Core\Boost\Campaigns\Delegates;
......@@ -79,9 +75,6 @@ class NormalizeEntityUrnsDelegate
}
}
$campaign
->setEntityUrns($entityUrns);
return $campaign;
return $campaign->setEntityUrns($entityUrns);
}
}
<?php
/**
* PaymentsDelegate
* @author edgebal
*/
namespace Minds\Core\Boost\Campaigns\Delegates;
......@@ -51,8 +47,6 @@ class PaymentsDelegate
*/
public function onCreate(Campaign $campaign, $paymentPayload = null)
{
// NOTE: Do not spec test. Individually test the other methods.
$this->validateBudget($campaign);
if (!$paymentPayload) {
......@@ -60,12 +54,9 @@ class PaymentsDelegate
}
$this->pay($campaign, $paymentPayload);
$this->validatePayments($campaign);
$campaign = $this->updateImpressionsByCpm($campaign);
return $campaign;
return $this->updateImpressionsByCpm($campaign);
}
/**
......@@ -77,11 +68,7 @@ class PaymentsDelegate
*/
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);
if ($paymentPayload) {
......@@ -90,9 +77,7 @@ class PaymentsDelegate
$this->validatePayments($campaignRef);
}
$campaign = $this->updateImpressionsByCpm($campaign);
return $campaign;
return $this->updateImpressionsByCpm($campaign);
}
/**
......@@ -102,8 +87,6 @@ class PaymentsDelegate
*/
public function onStateChange(Campaign $campaign)
{
// NOTE: Do not spec test. Individually test the other methods.
$isFinished = in_array($campaign->getDeliveryStatus(), [
Campaign::STATUS_REJECTED,
Campaign::STATUS_REVOKED,
......@@ -195,49 +178,29 @@ class PaymentsDelegate
public function refund(Campaign $campaign)
{
$latestPaymentSource = '';
// Sum up all payments
$paid = 0;
$refundThreshold = 0.1;
foreach ($campaign->getPayments() as $payment) {
// Sum!
$paid += $payment->getAmount();
if ($payment->getAmount() > 0 && $payment->getSource()) {
// Grab the latest wallet used by campaign's owner
$latestPaymentSource = $payment->getSource();
}
}
// If amount is < 0.1 (minimum fraction), don't refund
if ($paid <= 0.1) {
if ($paid <= $refundThreshold) {
return $campaign;
}
// Grab a fresh count of impressions met
$impressionsMet = $this->metrics
->setCampaign($campaign)
->getImpressionsMet();
// Calculate the cost of the impressions met
$impressionsMet = $this->metrics->setCampaign($campaign)->getImpressionsMet();
$cost = ($impressionsMet / 1000) * $campaign->cpm();
// Calculate the amount to be refunded
$amount = $paid - $cost;
// If amount is < 0.1 (minimum fraction), don't refund
if ($amount < 0.1) {
if ($amount < $refundThreshold) {
return $campaign;
}
// Execute refund
switch ($campaign->getBudgetType()) {
case 'tokens':
$payment = new Payment();
......@@ -249,8 +212,7 @@ class PaymentsDelegate
->setTimeCreated(time());
try {
$this->onchainPayments
->refund($payment);
$this->onchainPayments->refund($payment);
} catch (Exception $e) {
throw new CampaignException("Error registering refund: {$e->getMessage()}");
}
......
......@@ -2,14 +2,71 @@
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\EntitiesBuilder;
use Minds\Core\Security\ACL;
use Minds\Entities\Activity;
use Minds\Entities\User;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
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()
{
$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 @@
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\Metrics;
use Minds\Core\Boost\Campaigns\Payments\Onchain;
use Minds\Core\Boost\Campaigns\Payments\Payment;
use Minds\Core\Config;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
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()
{
$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);
}
}