feat(api): Implement API Resource Standard compliance

- Create BaseResource with formatDate() and formatDecimal() utilities
- Create 11 API Resource classes for all models
- Update all 6 controllers to return wrapped responses via wrapResource()
- Update frontend API client with unwrapResponse() helper
- Update all 63+ backend tests to expect 'data' wrapper
- Regenerate Scribe API documentation

BREAKING CHANGE: All API responses now wrap data in 'data' key per architecture spec.

Backend Tests: 70 passed, 5 failed (unrelated to data wrapper)
Frontend Unit: 10 passed
E2E Tests: 102 passed, 20 skipped
API Docs: Generated successfully

Refs: openspec/changes/api-resource-standard
This commit is contained in:
2026-02-19 14:51:56 -05:00
parent 1592c5be8d
commit 47068dabce
49 changed files with 2426 additions and 809 deletions

View File

@@ -2,13 +2,13 @@
namespace Tests\Feature\TeamMember;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;
use App\Models\TeamMember;
use App\Models\Role;
use App\Models\Allocation;
use App\Models\Project;
use App\Models\Role;
use App\Models\TeamMember;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class TeamMemberTest extends TestCase
{
@@ -52,11 +52,13 @@ class TeamMemberTest extends TestCase
$response->assertStatus(201);
$response->assertJson([
'name' => 'John Doe',
'role_id' => $role->id,
'hourly_rate' => '150.00',
'active' => true,
'data' => [
'name' => 'John Doe',
'hourly_rate' => '150.00',
'active' => true,
],
]);
$response->assertJsonPath('data.role.id', $role->id);
$this->assertDatabaseHas('team_members', [
'name' => 'John Doe',
@@ -123,7 +125,7 @@ class TeamMemberTest extends TestCase
->getJson('/api/team-members');
$response->assertStatus(200);
$response->assertJsonCount(3);
$response->assertJsonCount(3, 'data');
}
// 2.1.13 API test: Filter by active status
@@ -141,14 +143,14 @@ class TeamMemberTest extends TestCase
->getJson('/api/team-members?active=true');
$response->assertStatus(200);
$response->assertJsonCount(2);
$response->assertJsonCount(2, 'data');
// Get only inactive
$response = $this->withHeader('Authorization', "Bearer {$token}")
->getJson('/api/team-members?active=false');
$response->assertStatus(200);
$response->assertJsonCount(1);
$response->assertJsonCount(1, 'data');
}
// 2.1.14 API test: PUT /api/team-members/{id} updates member
@@ -168,8 +170,10 @@ class TeamMemberTest extends TestCase
$response->assertStatus(200);
$response->assertJson([
'id' => $teamMember->id,
'hourly_rate' => '175.00',
'data' => [
'id' => $teamMember->id,
'hourly_rate' => '175.00',
],
]);
$this->assertDatabaseHas('team_members', [
@@ -195,8 +199,10 @@ class TeamMemberTest extends TestCase
$response->assertStatus(200);
$response->assertJson([
'id' => $teamMember->id,
'active' => false,
'data' => [
'id' => $teamMember->id,
'active' => false,
],
]);
$this->assertDatabaseHas('team_members', [