Skip to content

Commit

Permalink
chore: tests and composer update
Browse files Browse the repository at this point in the history
  • Loading branch information
adiologydev committed Nov 20, 2024
1 parent a0e6691 commit 4774336
Show file tree
Hide file tree
Showing 9 changed files with 307 additions and 23 deletions.
18 changes: 8 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"name": "climactic/laravel-credits",
"description": "This is my package laravel-credits",
"description": "A ledger-based Laravel package for managing credit-based systems in your application.",
"keywords": [
"Climactic",
"laravel",
"laravel-credits"
"laravel-credits",
"balance",
"ledger",
"transactions"
],
"homepage": "https://github.com/climactic/laravel-credits",
"license": "MIT",
Expand All @@ -30,8 +32,7 @@
},
"autoload": {
"psr-4": {
"Climactic\\Credits\\": "src/",
"Climactic\\Credits\\Database\\Factories\\": "database/factories/"
"Climactic\\Credits\\": "src/"
}
},
"autoload-dev": {
Expand Down Expand Up @@ -69,12 +70,9 @@
"laravel": {
"providers": [
"Climactic\\Credits\\CreditsServiceProvider"
],
"aliases": {
"Credits": "Climactic\\Credits\\Facades\\Credits"
}
]
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
23 changes: 11 additions & 12 deletions src/Models/Credit.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,26 @@

class Credit extends Model
{
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
$this->setTable(config('credits.table_name', 'credits'));
}

protected $fillable = [
'amount',
'description',
'type',
'metadata',
'running_balance',
'creditable_type',
'creditable_id',
];

protected function casts(): array
protected $casts = [
'amount' => 'decimal:2',
'running_balance' => 'decimal:2',
'metadata' => 'array',
];

public function __construct(array $attributes = [])
{
return [
'amount' => 'decimal:' . config('credits.decimal_precision', 2),
'running_balance' => 'decimal:' . config('credits.decimal_precision', 2),
'metadata' => 'array',
];
parent::__construct($attributes);
$this->setTable(config('credits.table_name', 'credits'));
}

public function creditable(): MorphTo
Expand Down
3 changes: 2 additions & 1 deletion src/Traits/HasCredits.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ public function getTransactionHistory(int $limit = 10, string $order = 'desc'):
{
return $this->creditTransactions()
->orderBy('created_at', $order)
->cursorPaginate($limit);
->limit($limit)
->get();
}

/**
Expand Down
76 changes: 76 additions & 0 deletions tests/CreditTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?php

use Climactic\Credits\Models\Credit;
use Climactic\Credits\Tests\TestModels\User;

beforeEach(function () {
$this->user = User::create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
});

it('can create a credit transaction', function () {
$credit = Credit::create([
'creditable_type' => User::class,
'creditable_id' => $this->user->id,
'amount' => 100.00,
'running_balance' => 100.00,
'description' => 'Test credit',
'type' => 'credit',
'metadata' => ['source' => 'test'],
]);

expect($credit)->toBeInstanceOf(Credit::class)
->and((float)$credit->amount)->toEqual(100.00)
->and($credit->type)->toBe('credit')
->and($credit->metadata)->toBe(['source' => 'test'])
->and($credit->creditable)->toBeInstanceOf(User::class);
});

it('casts amount and running_balance as decimals', function () {
$credit = Credit::create([
'creditable_type' => User::class,
'creditable_id' => $this->user->id,
'amount' => '100.50',
'running_balance' => '100.50',
'type' => 'credit',
]);

expect((float)$credit->amount)->toEqual(100.50)
->and((float)$credit->running_balance)->toEqual(100.50);
});

it('casts metadata as array', function () {
$credit = Credit::create([
'creditable_type' => User::class,
'creditable_id' => $this->user->id,
'amount' => 100,
'running_balance' => 100,
'type' => 'credit',
'metadata' => ['key' => 'value'],
]);

expect($credit->metadata)->toBeArray()
->and($credit->metadata)->toBe(['key' => 'value']);
});

it('has correct table name from config', function () {
config(['credits.table_name' => 'custom_credits']);
$credit = new Credit();

expect($credit->getTable())->toBe('custom_credits');
});

it('belongs to creditable model', function () {
$credit = Credit::create([
'creditable_type' => User::class,
'creditable_id' => $this->user->id,
'amount' => 100,
'running_balance' => 100,
'type' => 'credit',
]);

expect($credit->creditable)->toBeInstanceOf(User::class)
->and($credit->creditable->id)->toBe($this->user->id);
});
113 changes: 113 additions & 0 deletions tests/HasCreditsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?php

use Climactic\Credits\Tests\TestModels\User;
use Climactic\Credits\Exceptions\InsufficientCreditsException;

beforeEach(function () {
$this->user = User::create([
'name' => 'Test User',
'email' => 'test@example.com',
]);
});

it('can add credits', function () {
$transaction = $this->user->addCredits(100.00, 'Test credit');

expect((float)$transaction->amount)->toEqual(100.00)
->and($transaction->type)->toBe('credit')
->and((float)$transaction->running_balance)->toEqual(100.00)
->and((float)$this->user->getCurrentBalance())->toEqual(100.00);
});

it('can deduct credits', function () {
$this->user->addCredits(100.00);
$transaction = $this->user->deductCredits(50.00, 'Test debit');

expect((float)$transaction->amount)->toEqual(50.00)
->and($transaction->type)->toBe('debit')
->and((float)$transaction->running_balance)->toEqual(50.00)
->and((float)$this->user->getCurrentBalance())->toEqual(50.00);
});

it('prevents negative balance when configured', function () {
config(['credits.allow_negative_balance' => false]);

$this->user->addCredits(100.00);

expect(fn() => $this->user->deductCredits(150.00))
->toThrow(InsufficientCreditsException::class);
});

it('allows negative balance when configured', function () {
config(['credits.allow_negative_balance' => true]);

$this->user->addCredits(100.00);
$transaction = $this->user->deductCredits(150.00);

expect((float)$transaction->running_balance)->toEqual(-50.00)
->and((float)$this->user->getCurrentBalance())->toEqual(-50.00);
});

it('can transfer credits between users', function () {
$recipient = User::create([
'name' => 'Recipient User',
'email' => 'recipient@example.com',
]);

$this->user->addCredits(100.00);
$result = $this->user->transferCredits($recipient, 50.00, 'Test transfer');

expect($result['sender_balance'])->toBe(50.00)
->and($result['recipient_balance'])->toBe(50.00)
->and($this->user->getCurrentBalance())->toBe(50.00)
->and($recipient->getCurrentBalance())->toBe(50.00);
});

it('can get transaction history', function () {
$this->user->addCredits(100.00, 'First credit');
$this->user->deductCredits(30.00, 'First debit');
$this->user->addCredits(50.00, 'Second credit');

$history = $this->user->getTransactionHistory(10);

expect($history)->toHaveCount(3)
->and($history->first()->description)->toBe('Second credit');
});

it('can check if has enough credits', function () {
$this->user->addCredits(100.00);

expect($this->user->hasEnoughCredits(50.00))->toBeTrue()
->and($this->user->hasEnoughCredits(150.00))->toBeFalse();
});

it('can get balance as of date', function () {
$this->user->addCredits(100.00);

// Move time forward
$this->travel(1)->days();

$this->user->addCredits(50.00);

$pastDate = now()->subDay();
$balance = $this->user->getBalanceAsOf($pastDate);

expect($balance)->toBe(100.00);
});

it('maintains accurate running balance', function () {
$transactions = collect([
$this->user->addCredits(100.00),
$this->user->deductCredits(30.00),
$this->user->addCredits(50.00),
$this->user->deductCredits(20.00),
]);

$expectedBalances = [100.00, 70.00, 120.00, 100.00];

$transactions->each(function ($transaction, $index) use ($expectedBalances) {
expect((float)$transaction->running_balance)->toEqual($expectedBalances[$index]);
});

expect((float)$this->user->getCurrentBalance())->toEqual(100.00);
});
5 changes: 5 additions & 0 deletions tests/Pest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

use Climactic\Credits\Tests\TestCase;

uses(TestCase::class)->in(__DIR__);
54 changes: 54 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace Climactic\Credits\Tests;

use Illuminate\Database\Eloquent\Factories\Factory;
use Orchestra\Testbench\TestCase as Orchestra;
use Climactic\Credits\CreditsServiceProvider;

class TestCase extends Orchestra
{
protected function setUp(): void
{
parent::setUp();

Factory::guessFactoryNamesUsing(
fn (string $modelName) => 'Climactic\\Credits\\Database\\Factories\\'.class_basename($modelName).'Factory'
);
}

protected function getPackageProviders($app)
{
return [
CreditsServiceProvider::class,
];
}

public function getEnvironmentSetUp($app)
{
// Use SQLite in memory for testing
config()->set('database.default', 'testing');
config()->set('database.connections.testing', [
'driver' => 'sqlite',
'database' => ':memory:',
'prefix' => '',
]);

// Set up credits config
config()->set('credits.allow_negative_balance', false);
config()->set('credits.decimal_precision', 2);
config()->set('credits.table_name', 'credits');
config()->set('credits.description.required', false);
config()->set('credits.description.max_length', 255);
}

protected function defineDatabaseMigrations()
{
// Include the package migrations
include_once __DIR__.'/../database/migrations/create_credits_table.php.stub';
(include __DIR__.'/../database/migrations/create_credits_table.php.stub')->up();

// Include the test migrations
$this->loadMigrationsFrom(__DIR__ . '/database/migrations');
}
}
15 changes: 15 additions & 0 deletions tests/TestModels/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace Climactic\Credits\Tests\TestModels;

use Illuminate\Foundation\Auth\User as Authenticatable;
use Climactic\Credits\Traits\HasCredits;

class User extends Authenticatable
{
use HasCredits;

protected $guarded = [];

protected $table = 'users';
}
23 changes: 23 additions & 0 deletions tests/database/migrations/create_users_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('users');
}
};

0 comments on commit 4774336

Please # to comment.