AiModelCrudTest.php 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <?php
  2. use App\Models\AiModel;
  3. use Illuminate\Foundation\Testing\RefreshDatabase;
  4. use Illuminate\Support\Str;
  5. uses(RefreshDatabase::class);
  6. it('stores every field the client sends', function () {
  7. $ownerId = makeStudio('tester');
  8. $this->postJson('/api/v2/ai-model', [
  9. 'studio_name' => 'tester',
  10. 'name' => 'claude-opus-5',
  11. 'model' => 'claude-opus-5-20260101',
  12. 'url' => 'https://api.anthropic.com',
  13. 'key' => 'sk-ant-test',
  14. 'privacy' => 'public',
  15. 'description' => 'writes sentences',
  16. ], authHeader($ownerId))->assertOk();
  17. $saved = AiModel::where('owner_id', $ownerId)->where('name', 'claude-opus-5')->first();
  18. // 修复前 store() 只写 name/uid/real_name/owner_id/editor_id,其余全丢
  19. expect($saved->model)->toBe('claude-opus-5-20260101');
  20. expect($saved->url)->toBe('https://api.anthropic.com');
  21. expect($saved->key)->toBe('sk-ant-test');
  22. expect($saved->privacy)->toBe('public');
  23. expect($saved->description)->toBe('writes sentences');
  24. });
  25. it('defaults privacy to private', function () {
  26. $ownerId = makeStudio('tester');
  27. $this->postJson('/api/v2/ai-model', [
  28. 'studio_name' => 'tester',
  29. 'name' => 'no-privacy-given',
  30. ], authHeader($ownerId))->assertOk();
  31. expect(AiModel::where('owner_id', $ownerId)->first()->privacy)->toBe('private');
  32. });
  33. it('refuses a duplicate name inside the same studio', function () {
  34. $ownerId = makeStudio('tester');
  35. AiModel::factory()->ownedBy($ownerId)->create(['name' => 'claude-opus-5']);
  36. $this->postJson('/api/v2/ai-model', [
  37. 'studio_name' => 'tester',
  38. 'name' => 'claude-opus-5',
  39. ], authHeader($ownerId))->assertStatus(409);
  40. expect(AiModel::where('owner_id', $ownerId)->count())->toBe(1);
  41. });
  42. it('requires a name', function () {
  43. $ownerId = makeStudio('tester');
  44. $this->postJson('/api/v2/ai-model', [
  45. 'studio_name' => 'tester',
  46. ], authHeader($ownerId))->assertStatus(422);
  47. });
  48. it('updates only the fields present in the request', function () {
  49. $owner = (string) Str::uuid();
  50. $model = AiModel::factory()->ownedBy($owner)->create([
  51. 'name' => 'claude-opus-5',
  52. 'key' => 'sk-keep-me',
  53. 'system_prompt' => 'keep me too',
  54. 'url' => 'https://api.anthropic.com',
  55. ]);
  56. // 只改 model 一个字段——这正是 Skill 的 ensure-model 会发的局部 PUT
  57. $this->putJson("/api/v2/ai-model/{$model->uid}", [
  58. 'model' => 'claude-opus-5-20260101',
  59. ], authHeader($owner))->assertOk();
  60. $model->refresh();
  61. expect($model->model)->toBe('claude-opus-5-20260101');
  62. // 修复前这些字段会被 input() 的 null 覆盖掉
  63. expect($model->key)->toBe('sk-keep-me');
  64. expect($model->system_prompt)->toBe('keep me too');
  65. expect($model->url)->toBe('https://api.anthropic.com');
  66. expect($model->name)->toBe('claude-opus-5');
  67. });
  68. it('still allows clearing a field explicitly', function () {
  69. $owner = (string) Str::uuid();
  70. $model = AiModel::factory()->ownedBy($owner)->create(['description' => 'old text']);
  71. $this->putJson("/api/v2/ai-model/{$model->uid}", [
  72. 'description' => null,
  73. ], authHeader($owner))->assertOk();
  74. expect($model->refresh()->description)->toBeNull();
  75. });
  76. it('refuses to rename onto an existing name', function () {
  77. $owner = (string) Str::uuid();
  78. AiModel::factory()->ownedBy($owner)->create(['name' => 'taken']);
  79. $model = AiModel::factory()->ownedBy($owner)->create(['name' => 'mine']);
  80. $this->putJson("/api/v2/ai-model/{$model->uid}", [
  81. 'name' => 'taken',
  82. ], authHeader($owner))->assertStatus(409);
  83. expect($model->refresh()->name)->toBe('mine');
  84. });
  85. it('rejects an update from a non-owner', function () {
  86. $model = AiModel::factory()->create(['name' => 'untouched']);
  87. $this->putJson("/api/v2/ai-model/{$model->uid}", [
  88. 'name' => 'hijacked',
  89. ], authHeader((string) Str::uuid()))->assertStatus(403);
  90. expect($model->refresh()->name)->toBe('untouched');
  91. });