Skip to content

Commit

Permalink
Merge pull request #10 from luttje/feature/add-test-suite
Browse files Browse the repository at this point in the history
Add test suite
  • Loading branch information
rupadana authored Jan 5, 2024
2 parents eeae77f + 593bc3d commit 013d262
Show file tree
Hide file tree
Showing 20 changed files with 548 additions and 83 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,26 @@ and then you will got this routes:
- [POST] '/api/blogs' - Create resource
- [DELETE] '/api/blogs/1' - Delete resource


On CreateHandler, you need to be create your custom request validation.

Im using `"spatie/laravel-query-builder": "^5.3"` to handle query and filtering. u can see `"spatie/laravel-query-builder": "^5.3"` [https://spatie.be/docs/laravel-query-builder/v5/introduction](documentation)

Im using `"spatie/laravel-query-builder": "^5.3"` to handle query selecting, sorting and filtering. Check out [the spatie/laravel-query-builder documentation](https://spatie.be/docs/laravel-query-builder/v5/introduction) for more information.

You can specified `allowedFilters` and `allowedFields` in your model
You can specified `allowedFilters` and `allowedFields` in your model. For example:

Example
```php
class User extends Model {
// Which fields can be selected from the database through the query string
public static array $allowedFields = [
'name'
];

// Which fields can be used to sort the results through the query string
public static array $allowedSorts = [
'name',
'created_at'
];

// Which fields can be used to filter the results through the query string
public static array $allowedFilters = [
'name'
];
Expand Down Expand Up @@ -107,7 +107,7 @@ class BlogTransformer extends JsonResource

return [
"modified_name" => $this->name . ' so Cool!'
]
];
}
}
```
Expand Down
8 changes: 4 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@
"orchestra/testbench": "^8.0",
"pestphp/pest": "^2.0",
"pestphp/pest-plugin-arch": "^2.0",
"pestphp/pest-plugin-laravel": "^2.0"
"pestphp/pest-plugin-laravel": "^2.0",
"phpunit/phpunit": "^10.0.17"
},
"autoload": {
"psr-4": {
"Rupadana\\ApiService\\": "src/",
"Rupadana\\ApiService\\Database\\Factories\\": "database/factories/"
"Rupadana\\ApiService\\": "src/"
}
},
"autoload-dev": {
Expand Down Expand Up @@ -68,4 +68,4 @@
},
"minimum-stability": "dev",
"prefer-stable": true
}
}
19 changes: 0 additions & 19 deletions database/factories/ModelFactory.php

This file was deleted.

19 changes: 0 additions & 19 deletions database/migrations/create_api_service_table.php.stub

This file was deleted.

3 changes: 0 additions & 3 deletions src/ApiServiceServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ public function packageBooted(): void
], 'api-service-stubs');
}
}

// Testing
Testable::mixin(new TestsApiService());
}

protected function getAssetPackageName(): ?string
Expand Down
13 changes: 0 additions & 13 deletions src/Testing/TestsApiService.php

This file was deleted.

131 changes: 131 additions & 0 deletions tests/ApiServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

use Illuminate\Routing\Route as RoutingRoute;
use Rupadana\ApiService\Tests\Fixtures\Database\Seeders\ProductsSeeder;
use Rupadana\ApiService\Tests\Fixtures\Models\Product;

it('can make routes for a product resource', function () {
$routes = collect(app('router')->getRoutes())->map(function (RoutingRoute $route) {
return implode('|', $route->methods()) . ' ' . $route->uri();
});

// The route name is customized to `our-products` in the `ProductApiService` class
expect($routes)->toContain('POST api/our-products');
expect($routes)->toContain('PUT api/our-products/{id}');
expect($routes)->toContain('DELETE api/our-products/{id}');
expect($routes)->toContain('GET|HEAD api/our-products');
expect($routes)->toContain('GET|HEAD api/our-products/{id}');
});

it('can return a list of products with allowed attributes', function () {
$this->seed(ProductsSeeder::class);

$response = $this->get('/api/our-products')
->assertStatus(200);

$products = Product::all()->map(function ($product) {
return [
'name' => $product['name'],
'description' => $product['description'],
'price' => $product['price'],
];
})->toArray();

foreach ($products as $product) {
$response->assertJsonFragment($product);
}

// Check that the slug (hidden) is not returned
$response->assertJsonMissing([
'slug' => 't-shirt',
]);
});

it('can return a list of products with selected fields', function () {
$this->seed(ProductsSeeder::class);

$response = $this->get('/api/our-products?fields[products]=name,price')
->assertStatus(200);

$response->assertJsonFragment([
'name' => 'T-Shirt',
'price' => 500,
]);
});

it('throws when selecting a field that is not allowed', function () {
$this->seed(ProductsSeeder::class);

$this->get('/api/our-products?fields[products]=name,slug,price')
->assertStatus(400)
->assertJsonFragment([
'message' => 'Requested field(s) `products.slug` are not allowed.',
]);
})->throws(\Spatie\QueryBuilder\Exceptions\InvalidFieldQuery::class);

it('can return a list of products with selected sorts', function () {
$this->seed(ProductsSeeder::class);

$response = $this->get('/api/our-products?sort=-price')
->assertStatus(200);

$data = Product::all()
->sortByDesc('price')
->values()
->map(function ($product) {
return [
'name' => $product['name'],
'price' => $product['price'],
];
})
->toArray();

foreach ($data as $product) {
$response->assertJsonFragment($product);
}
});

it('throws when sorting by a field that is not allowed', function () {
$this->seed(ProductsSeeder::class);

$this->get('/api/our-products?sort=-slug')
->assertStatus(400)
->assertJsonFragment([
'message' => 'Requested sort(s) `products.slug` are not allowed.',
]);
})->throws(\Spatie\QueryBuilder\Exceptions\InvalidSortQuery::class);

it('can return a list of products with selected filters', function () {
$this->seed(ProductsSeeder::class);

$response = $this->get('/api/our-products?filter[name]=T-Shirt&filter[price]=500')
->assertStatus(200);

$response->assertJsonFragment([
'name' => 'T-Shirt',
'price' => 500,
]);
});

it('throws when filtering by a field that is not allowed', function () {
$this->seed(ProductsSeeder::class);

$this->get('/api/our-products?filter[name]=T-Shirt&filter[slug]=t-shirt')
->assertStatus(400)
->assertJsonFragment([
'message' => 'Requested filter(s) `products.slug` are not allowed.',
]);
})->throws(\Spatie\QueryBuilder\Exceptions\InvalidFilterQuery::class);

it('can return a list of products with a custom transformer', function () {
$this->seed(ProductsSeeder::class);

$response = $this->get('/api/our-products')
->assertStatus(200);

$product = Product::first();

$response->assertJsonFragment([
'hash' => md5($product['name']),
]);
});
5 changes: 0 additions & 5 deletions tests/ExampleTest.php

This file was deleted.

28 changes: 28 additions & 0 deletions tests/Fixtures/Database/Migrations/01_create_products_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?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('products', function (Blueprint $table) {
$table->id();

$table->string('name');
$table->string('slug')->unique();

$table->text('description')->nullable();

$table->decimal('price', 15, 2)->default(0);

$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('products');
}
};
61 changes: 61 additions & 0 deletions tests/Fixtures/Database/Seeders/ProductsSeeder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace Rupadana\ApiService\Tests\Fixtures\Database\Seeders;

use Illuminate\Database\Seeder;
use Rupadana\ApiService\Tests\Fixtures\Models\Product;

class ProductsSeeder extends Seeder
{
public function run()
{
Product::create([
'name' => 'Jeans',
'slug' => 'jeans',
'description' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.',
'price' => 1000,
]);

Product::create([
'name' => 'T-Shirt',
'slug' => 't-shirt',
'description' => 'Donec sed odio dui. Nullam quis risus eget urna mollis ornare vel eu leo.',
'price' => 500,
]);

Product::create([
'name' => 'Shoes',
'slug' => 'shoes',
'description' => 'Maecenas faucibus mollis interdum. Nullam id dolor id nibh ultricies vehicula ut id elit.',
'price' => 2000,
]);

Product::create([
'name' => 'Hat',
'slug' => 'hat',
'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.',
'price' => 100,
]);

Product::create([
'name' => 'Socks',
'slug' => 'socks',
'description' => 'Cras mattis consectetur purus sit amet fermentum.',
'price' => 50,
]);

Product::create([
'name' => 'Gloves',
'slug' => 'gloves',
'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.',
'price' => 150,
]);

Product::create([
'name' => 'Jacket',
'slug' => 'jacket',
'description' => 'Maecenas sed diam eget risus varius blandit sit amet non magna.',
'price' => 200,
]);
}
}
34 changes: 34 additions & 0 deletions tests/Fixtures/Models/Product.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

namespace Rupadana\ApiService\Tests\Fixtures\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
public static array $allowedFields = [
'name',
// 'slug' is not allowed
'description',
'price',
'created_at',
];

public static array $allowedSorts = [
'name',
'price',
'created_at',
];

public static array $allowedFilters = [
'name',
'price',
'created_at',
];

protected $guarded = [];

protected $hidden = [
'slug',
];
}
Loading

0 comments on commit 013d262

Please sign in to comment.