seed(RolePermissionSeeder::class); } protected function actingWithRole(string $role): User { $user = User::factory()->create(); $user->syncRoles([$role]); $this->withHeaders($this->apiHeadersFor($user)); return $user; } #[Test] public function a_batch_runs_each_operation_and_reports_each_answer(): void { $this->actingWithRole('editor'); $member = Member::factory()->create(); $this->postJson('/api/v1/batch', [ 'operations' => [ ['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => 'Vue', 'slug' => 'vue']], ['method' => 'PATCH', 'path' => "/v1/members/{$member->id}", 'body' => ['github_username' => 'ada']], ['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => '']], ['method' => 'GET', 'path' => '/v1/me'], ], ]) ->assertOk() ->assertJsonPath('data.0.status', 201) ->assertJsonPath('data.0.body.data.slug', 'vue') ->assertJsonPath('data.1.status', 200) ->assertJsonPath('data.2.status', 422) ->assertJsonPath('data.3.status', 200) ->assertJsonPath('meta.committed', true) ->assertJsonPath('meta.failed', 1); $this->assertSame('ada', $member->fresh()->github_username); $this->assertTrue(Technology::query()->where('slug', 'vue')->exists()); } #[Test] public function an_atomic_batch_keeps_nothing_when_one_fails(): void { $this->actingWithRole('editor'); $this->postJson('/api/v1/batch', [ 'atomic' => true, 'operations' => [ ['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => 'Vue', 'slug' => 'vue']], ['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => '']], ['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => 'Never', 'slug' => 'never']], ], ]) ->assertOk() ->assertJsonCount(2, 'data') ->assertJsonPath('meta.committed', false); $this->assertSame(0, Technology::query()->count()); } #[Test] public function a_batch_cannot_do_what_its_caller_cannot(): void { $this->actingWithRole('viewer'); $this->postJson('/api/v1/batch', [ 'operations' => [['method' => 'POST', 'path' => '/v1/technologies', 'body' => ['name' => 'Vue', 'slug' => 'vue']]], ])->assertJsonPath('data.0.status', 403); $this->assertSame(0, Technology::query()->count()); } #[Test] public function batches_do_not_nest_or_leave_the_admin_api(): void { $this->actingWithRole('editor'); foreach (['/v1/batch', '/v1/import/pages', '/v1/auth/token', '/public/v1/site', '/v1/../public/v1/site'] as $path) { $this->postJson('/api/v1/batch', ['operations' => [['method' => 'POST', 'path' => $path]]]) ->assertUnprocessable() ->assertJsonValidationErrors('operations.0.path'); } } #[Test] public function an_import_creates_new_rows_and_updates_matching_ones(): void { $this->actingWithRole('editor'); Technology::factory()->create(['name' => 'Angular', 'slug' => 'angular', 'website' => null]); $this->postJson('/api/v1/import/technologies', [ 'match' => 'slug', 'rows' => [ ['name' => 'Angular', 'slug' => 'angular', 'website' => 'https://angular.dev'], ['name' => 'Svelte', 'slug' => 'svelte', 'description' => ['es' => 'Compilador', 'en' => 'Compiler']], ], ]) ->assertOk() ->assertJsonPath('data.0.method', 'PATCH') ->assertJsonPath('data.1.method', 'POST') ->assertJsonPath('meta.succeeded', 2); $this->assertSame('https://angular.dev', Technology::query()->where('slug', 'angular')->value('website')); $this->assertSame(2, Technology::query()->count()); } #[Test] public function a_csv_import_reads_languages_lists_and_booleans(): void { $this->actingWithRole('editor'); [$vue, $php] = Technology::factory()->count(2)->create(); $csv = "\xEF\xBB\xBFname,slug,headline.es,headline.en,technology_ids,is_public,github_username\n" ."Ada Lovelace,ada,Backend,Backend dev,{$vue->id}|{$php->id},true,\n" ."Linus,linus,Sistemas,,,false,linus\n"; $this->post('/api/v1/import/members', [ 'file' => UploadedFile::fake()->createWithContent('members.csv', $csv), ], ['Accept' => 'application/json']) ->assertOk() ->assertJsonPath('meta.failed', 0); $ada = Member::query()->where('slug', 'ada')->sole(); $this->assertSame('Backend dev', $ada->getTranslation('headline', 'en')); $this->assertCount(2, $ada->technologies); $this->assertTrue($ada->is_public); $this->assertFalse(Member::query()->where('slug', 'linus')->sole()->is_public); } #[Test] public function a_dry_run_validates_everything_and_keeps_nothing(): void { $this->actingWithRole('editor'); $this->postJson('/api/v1/import/technologies', [ 'dry_run' => true, 'rows' => [ ['name' => 'Vue', 'slug' => 'vue'], ['name' => 'Svelte', 'slug' => 'svelte'], ], ]) ->assertOk() ->assertJsonPath('meta.dry_run', true) ->assertJsonPath('meta.committed', false) ->assertJsonPath('meta.succeeded', 2); $this->assertSame(0, Technology::query()->count()); } }