diff --git a/public/api/schema/options/category-collection.json b/public/api/schema/options/category-collection.json new file mode 100644 index 00000000..5bf06acd --- /dev/null +++ b/public/api/schema/options/category-collection.json @@ -0,0 +1,351 @@ +{ + "$id": "https://api.costs-to-expect.com/v2/api/schema/options/category-collection.json", + "type": "object", + "properties": { + "GET": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authentication": { + "type": "object", + "properties": { + "required": { + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "required", + "authenticated" + ] + }, + "sortable": { + "type": "array", + "items": { + "type": "string" + } + }, + "searchable": { + "type": "object", + "properties": { + "name": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + }, + "description": { + "type": "object", + "properties": { + "type": { + "type": "string" + } + } + } + }, + "required": [ + "name", + "description" + ] + }, + "filterable": { + "type": [ + "array" + ] + }, + "parameters": { + "type":"object", + "properties": { + "offset": { + "type": "object", + "properties": { + "parameter": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "parameter", + "title", + "description", + "default", + "type", + "required" + ] + }, + "limit": { + "type": "object", + "properties": { + "parameter": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "parameter", + "title", + "description", + "default", + "type", + "required" + ] + }, + "collection": { + "type": "object", + "properties": { + "parameter": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "parameter", + "title", + "description", + "default", + "type", + "required" + ] + }, + "sort": { + "type": "object", + "properties": { + "parameter": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": { + "type": "null" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "parameter", + "title", + "description", + "default", + "type", + "required" + ] + }, + "search": { + "type": "object", + "properties": { + "parameter": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": { + "type": "null" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "parameter", + "title", + "description", + "default", + "type", + "required" + ] + } + }, + "required": [ + "offset", + "limit", + "collection", + "sort", + "search" + ] + } + }, + "required": [ + "description", + "authentication", + "sortable", + "searchable", + "filterable", + "parameters" + ] + }, + "POST": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authentication": { + "type": "object", + "properties": { + "required": { + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "required", + "authenticated" + ] + }, + "fields": { + "type": "object", + "properties": { + "name": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "validation": { + "type": "object", + "properties": { + "unique-for": { + "type": "string" + }, + "max-length": { + "type": "integer" + } + }, + "required": [ + "max-length" + ] + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "field", + "title", + "description", + "type", + "validation", + "required" + ] + }, + "description": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "field", + "title", + "description", + "type", + "required" + ] + } + }, + "required": [ + "name", + "description" + ] + } + }, + "required": [ + "description", + "authentication", + "fields", + "parameters" + ] + } + }, + "required": [ + "GET", + "POST" + ] +} diff --git a/public/api/schema/options/category.json b/public/api/schema/options/category.json new file mode 100644 index 00000000..87bdfb86 --- /dev/null +++ b/public/api/schema/options/category.json @@ -0,0 +1,219 @@ +{ + "$id": "https://api.costs-to-expect.com/v2/api/schema/options/category.json", + "type": "object", + "properties": { + "DELETE": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authentication": { + "type": "object", + "properties": { + "required": { + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "required", + "authenticated" + ] + } + }, + "required": [ + "description", + "authentication" + ] + }, + "GET" : { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authentication": { + "type": "object", + "properties": { + "required": { + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "required", + "authenticated" + ] + }, + "sortable": { + "type": "array" + }, + "searchable": { + "type": "array" + }, + "filterable": { + "type": "array" + }, + "parameters": { + "type": "object", + "properties": { + "include-subcategories": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "field", + "title", + "description", + "type", + "required" + ] + } + }, + "required": [ + "include-subcategories" + ] + } + }, + "required": [ + "description", + "authentication", + "sortable", + "searchable", + "filterable", + "parameters" + ] + }, + "PATCH": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "authentication": { + "type": "object", + "properties": { + "required": { + "type": "boolean" + }, + "authenticated": { + "type": "boolean" + } + }, + "required": [ + "required", + "authenticated" + ] + }, + "fields": { + "type": "object", + "properties": { + "name": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "validation": { + "type": "object", + "properties": { + "unique-for": { + "type": "string" + }, + "max-length": { + "type": "integer" + } + }, + "required": [ + "max-length" + ] + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "field", + "title", + "description", + "type", + "validation", + "required" + ] + }, + "description": { + "type": "object", + "properties": { + "field": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string" + }, + "required": { + "type": "boolean" + } + }, + "required": [ + "field", + "title", + "description", + "type", + "required" + ] + } + }, + "required": [ + "name", + "description" + ] + } + }, + "required": [ + "description", + "authentication", + "fields" + ] + } + }, + "required": [ + "DELETE", + "GET", + "PATCH" + ] +} diff --git a/routes/api/category.php b/routes/api/category.php index 85da57a5..a2fe4090 100644 --- a/routes/api/category.php +++ b/routes/api/category.php @@ -16,12 +16,12 @@ static function () { Route::get( 'resource-types/{resource_type_id}/categories', [App\Http\Controllers\View\CategoryController::class, 'index'] - ); + )->name('category.list'); Route::options( 'resource-types/{resource_type_id}/categories', [App\Http\Controllers\View\CategoryController::class, 'optionsIndex'] - ); + )->name('category.list.options'); Route::get( 'resource-types/{resource_type_id}/categories/{category_id}', @@ -31,7 +31,7 @@ static function () { Route::options( 'resource-types/{resource_type_id}/categories/{category_id}', [App\Http\Controllers\View\CategoryController::class, 'optionsShow'] - ); + )->name('category.show.options'); Route::get( diff --git a/tests/Action/Http/Controllers/CategoryTest.php b/tests/Action/Http/Controllers/CategoryTest.php index 217ca33b..a710ca0c 100644 --- a/tests/Action/Http/Controllers/CategoryTest.php +++ b/tests/Action/Http/Controllers/CategoryTest.php @@ -16,7 +16,7 @@ public function createCategoryFailsNoDescriptionInPayload(): void $id = $this->createAllocatedExpenseResourceType(); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $id, [ 'name' => $this->faker->text(200), @@ -33,7 +33,7 @@ public function createCategoryFailsNoNameInPayload(): void $id = $this->createAllocatedExpenseResourceType(); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $id, [ 'description' => $this->faker->text(200), @@ -57,7 +57,7 @@ public function createCategoryFailsNoPermissionToResourceType(): void $resource_type_id = (new Hash())->encode('resource-type', $resource_type->id); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $resource_type_id, [ 'name' => $this->faker->text(200), @@ -79,7 +79,7 @@ public function createCategoryFailsNoPayload(): void $id = $this->createAllocatedExpenseResourceType(); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $id, [] ); @@ -96,7 +96,7 @@ public function createCategoryFailsNonUniqueName(): void $name = $this->faker->text(200); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $id, [ 'name' => $name, @@ -125,7 +125,7 @@ public function createCategorySuccess(): void $id = $this->createAllocatedExpenseResourceType(); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $id, [ 'name' => $this->faker->text(200), @@ -137,6 +137,21 @@ public function createCategorySuccess(): void $this->assertJsonMatchesCategorySchema($response->content()); } + /** @test */ + public function deleteCategoryFailsIdInvalid(): void + { + $this->actingAs(User::find(1)); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $id = $this->createRandomCategory($resource_type_id); + + $response = $this->deleteRequestedCategory($resource_type_id, 'ABCDEDFGFG'); + $response->assertStatus(403); + + $response = $this->deleteRequestedCategory($resource_type_id, $id); + $response->assertStatus(204); + } + /** @test */ public function deleteCategorySuccess(): void { @@ -195,7 +210,7 @@ public function updateCategoryFailsNonUniqueName(): void // Create first category $name = $this->faker->text(200); - $response = $this->createRequestedCategory( + $response = $this->createCategory( $resource_type_id, [ 'name' => $name, @@ -206,7 +221,7 @@ public function updateCategoryFailsNonUniqueName(): void $response->assertStatus(201); // Create second category - $response = $this->createRequestedCategory( + $response = $this->createCategory( $resource_type_id, [ 'name' => $this->faker->text(200), diff --git a/tests/Action/Http/Controllers/SubcategoryManageTest.php b/tests/Action/Http/Controllers/SubcategoryTest.php similarity index 99% rename from tests/Action/Http/Controllers/SubcategoryManageTest.php rename to tests/Action/Http/Controllers/SubcategoryTest.php index 9ad985b6..1ae2d1f8 100644 --- a/tests/Action/Http/Controllers/SubcategoryManageTest.php +++ b/tests/Action/Http/Controllers/SubcategoryTest.php @@ -5,7 +5,7 @@ use App\User; use Tests\TestCase; -final class SubcategoryManageTest extends TestCase +final class SubcategoryTest extends TestCase { /** @test */ public function createSubcategoryFailsNoDescriptionInPayload(): void diff --git a/tests/TestCase.php b/tests/TestCase.php index 3bcb5f16..e7550c09 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -116,15 +116,21 @@ protected function assertProvidedJsonMatchesDefinedSchema($content, $schema_file } } - protected function createRandomCategory(string $resource_type_id): string + protected function createRandomCategory( + string $resource_type_id, + array $override = [] + ): string { - $response = $this->createRequestedCategory( - $resource_type_id, - [ - 'name' => $this->faker->text(200), - 'description' => $this->faker->text(200), - ] - ); + $payload = [ + 'name' => $this->faker->text(200), + 'description' => $this->faker->text(200), + ]; + + foreach ($override as $k => $v) { + $payload[$k] = $v; + } + + $response = $this->createCategory($resource_type_id, $payload); if ($response->assertStatus(201)) { return $response->json('id'); @@ -408,7 +414,7 @@ protected function createRandomSubcategory(string $resource_type_id, string $cat $this->fail('Unable to create the subcategory'); } - protected function createRequestedCategory(string $resource_type_id, array $payload): TestResponse + protected function createCategory(string $resource_type_id, array $payload): TestResponse { return $this->post( route('category.create', ['resource_type_id' => $resource_type_id]), @@ -644,6 +650,11 @@ protected function fetchAllPermittedUsers(array $parameters = []): TestResponse return $this->route('permitted-user.list', $parameters); } + protected function fetchCategoryCollection(array $parameters = []): TestResponse + { + return $this->route('category.list', $parameters); + } + protected function fetchItem(array $parameters = []): TestResponse { return $this->route('item.show', $parameters); @@ -684,6 +695,16 @@ protected function fetchResourceType(array $parameters = []): TestResponse return $this->route('resource-type.show', $parameters); } + protected function fetchOptionsForCategory(array $parameters = []): TestResponse + { + return $this->optionsRoute('category.show.options', $parameters); + } + + protected function fetchOptionsForCategoryCollection(array $parameters = []): TestResponse + { + return $this->optionsRoute('category.list.options', $parameters); + } + protected function fetchOptionsForCreatePassword(array $parameters = []): TestResponse { return $this->optionsRoute('auth.create-password.options', $parameters); diff --git a/tests/View/Http/Controllers/CategoryTest.php b/tests/View/Http/Controllers/CategoryTest.php new file mode 100644 index 00000000..7c581de2 --- /dev/null +++ b/tests/View/Http/Controllers/CategoryTest.php @@ -0,0 +1,544 @@ +actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 3); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionPagination(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'offset' => 0, + 'limit' => 2 + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 2); + $response->assertHeader('X-Offset', 0); + $response->assertHeader('X-Limit', 2); + $response->assertHeader('X-Link-Previous', ""); + $response->assertHeader('X-Link-Next', '/v3/resource-types/' . $resource_type_id . '/categories?offset=2&limit=2'); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionPaginationPrevious(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'offset' => 2, + 'limit' => 2 + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 1); + $response->assertHeader('X-Offset', 2); + $response->assertHeader('X-Limit', 2); + $response->assertHeader('X-Link-Previous', '/v3/resource-types/' . $resource_type_id . '/categories?offset=0&limit=2'); + $response->assertHeader('X-Link-Next', ''); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionSearchDescription(): void + { + $this->actingAs(User::find($this->createUser())); + + $search_string = $this->faker->text(100); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id, ['description' => $search_string]); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'search' => 'description:' . $search_string + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Search', 'description:' . urlencode($search_string)); + $response->assertHeader('X-Count', 1); + $this->assertEquals($search_string, $response->json()[0]['description']); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionSearchName(): void + { + $this->actingAs(User::find($this->createUser())); + + $search_string = $this->faker->text(25); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id, ['name' => $search_string]); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'search' => 'name:' . $search_string + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Search', 'name:' . urlencode($search_string)); + $response->assertHeader('X-Count', 1); + $this->assertEquals($search_string, $response->json()[0]['name']); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionSortCreated(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + sleep(2); // Ensure the created_at timestamps are different + $this->createRandomCategory($resource_type_id, ['name' => 'created-last']); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'sort'=>'created:desc' + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Sort', 'created:desc'); + $response->assertHeader('X-Count', 3); + $this->assertEquals('created-last', $response->json()[0]['name']); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionSortDescription(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id, ['description' => 'ZZZZZZZZZZZZA']); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'sort' => 'description:desc' + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Sort', 'description:desc'); + $response->assertHeader('X-Count', 3); + $this->assertEquals('ZZZZZZZZZZZZA', $response->json()[0]['description']); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function allocatedExpenseCategoryCollectionSortName(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id, ['name' => 'AAAAAAAAAAAAB']); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id, + 'sort' => 'name:asc' + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Sort', 'name:asc'); + $response->assertHeader('X-Count', 3); + $this->assertEquals('AAAAAAAAAAAAB', $response->json()[0]['name']); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function budgetCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + // This test will change later when we remove access to categories for + // the Budget and Budget pro item types, for now it is accessible + $resource_type_id = $this->createBudgetResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 3); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function budgetProCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + // This test will change later when we remove access to categories for + // the Budget and Budget pro item types, for now it is accessible + $resource_type_id = $this->createBudgetProResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 3); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** + * @test + * @runInSeparateProcess + * @preserveGlobalState disabled + */ + public function gameCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createGameResourceType(); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + + $response->assertStatus(200); + $response->assertHeader('X-Total-Count', 3); + $response->assertHeader('X-Count', 3); + + foreach ($response->json() as $item) { + try { + $json = json_encode($item, JSON_THROW_ON_ERROR); + } catch (\JsonException $e) { + $this->fail('Unable to encode the JSON string'); + } + + $this->assertJsonMatchesCategorySchema($json); + } + } + + /** @test */ + public function optionsRequestForAllocatedExpenseCategory(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $category_id = $this->createRandomCategory($resource_type_id); + + $response = $this->fetchOptionsForCategory([ + 'resource_type_id' => $resource_type_id, + 'category_id' => $category_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category.json'); + } + + /** @test */ + public function optionsRequestForAllocatedExpenseCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createAllocatedExpenseResourceType(); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchOptionsForCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category-collection.json'); + } + + /** @test */ + public function optionsRequestForBudgetCategory(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createBudgetResourceType(); + $category_id = $this->createRandomCategory($resource_type_id); + + // This test will be modified later when access to categories is + // removed for the Budget and Budget pro item types, for now it is accessible + $response = $this->fetchOptionsForCategory([ + 'resource_type_id' => $resource_type_id, + 'category_id' => $category_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category.json'); + } + + /** @test */ + public function optionsRequestForBudgetCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createBudgetResourceType(); + $this->createRandomCategory($resource_type_id); + + // This test will be modified later when we disable categories for the Budget + // and Budget pro item type, for now it is accessible + $response = $this->fetchOptionsForCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category-collection.json'); + } + + /** @test */ + public function optionsRequestForBudgetProCategory(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createBudgetProResourceType(); + $category_id = $this->createRandomCategory($resource_type_id); + + // This test will be modified later when access to categories is + // removed for the Budget and Budget pro item types, for now it is accessible + $response = $this->fetchOptionsForCategory([ + 'resource_type_id' => $resource_type_id, + 'category_id' => $category_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category.json'); + } + + /** @test */ + public function optionsRequestForBudgetProCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createBudgetProResourceType(); + $this->createRandomCategory($resource_type_id); + + // This test will be modified later when we disable categories for the Budget + // and Budget pro item type, for now it is accessible + $response = $this->fetchOptionsForCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category-collection.json'); + } + + /** @test */ + public function optionsRequestForGameCategory(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createGameResourceType(); + $category_id = $this->createRandomCategory($resource_type_id); + + $response = $this->fetchOptionsForCategory([ + 'resource_type_id' => $resource_type_id, + 'category_id' => $category_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category.json'); + } + + /** @test */ + public function optionsRequestForGameCategoryCollection(): void + { + $this->actingAs(User::find($this->createUser())); + + $resource_type_id = $this->createGameResourceType(); + $this->createRandomCategory($resource_type_id); + + $response = $this->fetchOptionsForCategoryCollection([ + 'resource_type_id' => $resource_type_id + ]); + $response->assertStatus(200); + + $this->assertProvidedJsonMatchesDefinedSchema($response->content(), 'api/schema/options/category-collection.json'); + } +}