insertGetId([ 'name' => 'Photography', 'slug' => 'photography-' . Str::lower(Str::random(6)), 'description' => null, 'created_at' => now(), 'updated_at' => now(), ]); return DB::table('categories')->insertGetId([ 'content_type_id' => $contentTypeId, 'parent_id' => null, 'name' => 'Nature', 'slug' => 'nature-' . Str::lower(Str::random(6)), 'description' => null, 'image' => null, 'is_active' => true, 'sort_order' => 0, 'created_at' => now(), 'updated_at' => now(), ]); } function createDraftUploadForAutosave(int $userId, string $status = 'draft'): string { $id = (string) Str::uuid(); DB::table('uploads')->insert([ 'id' => $id, 'user_id' => $userId, 'type' => 'image', 'status' => $status, 'title' => 'Original Title', 'description' => 'Original Description', 'license' => 'default', 'nsfw' => false, 'created_at' => now(), 'updated_at' => now(), ]); return $id; } it('owner can autosave', function () { Storage::fake('local'); $owner = User::factory()->create(); $categoryId = createCategoryForAutosaveTests(); $uploadId = createDraftUploadForAutosave($owner->id); $response = $this->actingAs($owner)->postJson("/api/uploads/{$uploadId}/autosave", [ 'title' => 'Updated Title', 'category_id' => $categoryId, 'description' => 'Updated Description', 'tags' => ['night', 'city'], 'license' => 'cc-by', 'nsfw' => true, ]); $response->assertOk()->assertJsonStructure([ 'success', 'updated_at', ])->assertJson([ 'success' => true, ]); $this->assertDatabaseHas('uploads', [ 'id' => $uploadId, 'title' => 'Updated Title', 'category_id' => $categoryId, 'description' => 'Updated Description', 'license' => 'cc-by', 'nsfw' => 1, ]); $row = DB::table('uploads')->where('id', $uploadId)->first(); expect(json_decode((string) $row->tags, true))->toBe(['night', 'city']); }); it('partial update works', function () { Storage::fake('local'); $owner = User::factory()->create(); $uploadId = createDraftUploadForAutosave($owner->id); $before = DB::table('uploads')->where('id', $uploadId)->first(); $response = $this->actingAs($owner)->postJson("/api/uploads/{$uploadId}/autosave", [ 'title' => 'Only Title Changed', ]); $response->assertOk()->assertJson([ 'success' => true, ]); $after = DB::table('uploads')->where('id', $uploadId)->first(); expect($after->title)->toBe('Only Title Changed'); expect($after->description)->toBe($before->description); expect($after->license)->toBe($before->license); }); it('guest denied', function () { Storage::fake('local'); $owner = User::factory()->create(); $uploadId = createDraftUploadForAutosave($owner->id); $response = $this->postJson("/api/uploads/{$uploadId}/autosave", [ 'title' => 'Nope', ]); expect(in_array($response->getStatusCode(), [401, 403]))->toBeTrue(); }); it('other user denied', function () { Storage::fake('local'); $owner = User::factory()->create(); $other = User::factory()->create(); $uploadId = createDraftUploadForAutosave($owner->id); $response = $this->actingAs($other)->postJson("/api/uploads/{$uploadId}/autosave", [ 'title' => 'Hacker Update', ]); $response->assertStatus(403); }); it('published upload rejected', function () { Storage::fake('local'); $owner = User::factory()->create(); $uploadId = createDraftUploadForAutosave($owner->id, 'published'); $response = $this->actingAs($owner)->postJson("/api/uploads/{$uploadId}/autosave", [ 'title' => 'Should Not Save', ]); $response->assertStatus(422); }); it('invalid category rejected', function () { Storage::fake('local'); $owner = User::factory()->create(); $uploadId = createDraftUploadForAutosave($owner->id); $response = $this->actingAs($owner)->postJson("/api/uploads/{$uploadId}/autosave", [ 'category_id' => 999999, ]); $response->assertStatus(422)->assertJsonValidationErrors(['category_id']); });