ProgressV3Test.php 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. <?php
  2. use App\Models\ProgressChapter;
  3. use Illuminate\Foundation\Testing\RefreshDatabase;
  4. use Illuminate\Support\Str;
  5. uses(RefreshDatabase::class);
  6. /**
  7. * GET /v3/progress —— 按 channel 列章节翻译进度。
  8. *
  9. * 这个端点原来零测试覆盖,整改(去掉只有一个合法值的 view 开关、加 FormRequest、
  10. * order 白名单、逻辑下沉 Service)时补上。
  11. */
  12. function makeProgressChapter(string $channelUid, int $book, int $para, array $attrs = []): ProgressChapter
  13. {
  14. $row = new ProgressChapter;
  15. $row->forceFill(array_merge([
  16. 'id' => random_int(1, PHP_INT_MAX),
  17. 'uid' => (string) Str::uuid(),
  18. 'book' => $book,
  19. 'para' => $para,
  20. 'lang' => 'zh-Hans',
  21. 'channel_id' => $channelUid,
  22. 'progress' => 0.5,
  23. 'title' => "book {$book} para {$para}",
  24. // all_trans / public 是 NOT NULL 且无默认值
  25. 'all_trans' => 0,
  26. 'public' => 0,
  27. ], $attrs))->save();
  28. return $row;
  29. }
  30. it('lists chapters of a channel', function () {
  31. $channel = makeChannel(makeStudio('prog-owner'), 'prog channel');
  32. makeProgressChapter($channel, 9001, 1);
  33. makeProgressChapter($channel, 9001, 2);
  34. makeProgressChapter((string) Str::uuid(), 9001, 3); // 别的 channel
  35. $json = $this->getJson("/api/v3/progress?channels={$channel}")->assertOk()->json();
  36. expect($json['data'])->toHaveCount(2)
  37. ->and($json['data'][0])->toHaveKeys(['book', 'para', 'lang', 'progress', 'channel_id'])
  38. ->and($json['meta']['total'])->toBe(2);
  39. });
  40. it('accepts an underscore separated channel list', function () {
  41. $a = makeChannel(makeStudio('prog-a'), 'a');
  42. $b = makeChannel(makeStudio('prog-b'), 'b');
  43. makeProgressChapter($a, 9001, 1);
  44. makeProgressChapter($b, 9002, 1);
  45. // 分隔符是下划线不是逗号,这是既有契约
  46. $data = $this->getJson("/api/v3/progress?channels={$a}_{$b}")->assertOk()->json('data');
  47. expect($data)->toHaveCount(2);
  48. });
  49. it('filters by book and lang', function () {
  50. $channel = makeChannel(makeStudio('prog-filter'), 'c');
  51. makeProgressChapter($channel, 9001, 1, ['lang' => 'zh-Hans']);
  52. makeProgressChapter($channel, 9002, 1, ['lang' => 'en']);
  53. expect($this->getJson("/api/v3/progress?channels={$channel}&book=9001")->assertOk()->json('data'))
  54. ->toHaveCount(1);
  55. expect($this->getJson("/api/v3/progress?channels={$channel}&lang=en")->assertOk()->json('data'))
  56. ->toHaveCount(1);
  57. });
  58. it('requires the channels filter', function () {
  59. // 不带条件就能拉全表的列表端点是设计错误(硬规范第 4 条)
  60. $this->getJson('/api/v3/progress')
  61. ->assertStatus(422)
  62. ->assertJsonStructure(['errors' => ['channels']]);
  63. });
  64. it('rejects an order column outside the whitelist', function () {
  65. $channel = makeChannel(makeStudio('prog-order'), 'c');
  66. // 原来 order 未校验,直接拼进 orderBy——传个不存在的列名就是 QueryException → 500
  67. $this->getJson("/api/v3/progress?channels={$channel}&order=no_such_column")
  68. ->assertStatus(422)
  69. ->assertJsonStructure(['errors' => ['order']]);
  70. $this->getJson("/api/v3/progress?channels={$channel}&order=updated_at")->assertOk();
  71. });
  72. it('rejects a bad dir or per_page', function () {
  73. $channel = makeChannel(makeStudio('prog-bad'), 'c');
  74. $this->getJson("/api/v3/progress?channels={$channel}&dir=sideways")->assertStatus(422);
  75. $this->getJson("/api/v3/progress?channels={$channel}&per_page=100000")->assertStatus(422);
  76. });
  77. it('no longer needs the view parameter', function () {
  78. $channel = makeChannel(makeStudio('prog-view'), 'c');
  79. makeProgressChapter($channel, 9001, 1);
  80. // view 只有一个合法值,是噪音,已去掉;传了也只是被忽略
  81. $this->getJson("/api/v3/progress?channels={$channel}")->assertOk();
  82. $this->getJson("/api/v3/progress?channels={$channel}&view=channel")->assertOk();
  83. });