article-api.spec.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. /**
  2. * 【API级】文章管理全流程
  3. * ================================================================
  4. * 对应 DOM 版:e2e/article-management.spec.js(远程无 SPA,DOM 版不可跑)
  5. * 接口契约来源:ArticleController(/api/articles|article/*)、ArticleCategoryController
  6. * list : { page?, size?, ... } 文章列表(Page)
  7. * detail : { id } 文章详情
  8. * featured : {} 精选文章
  9. * categories: {} 文章分类(公开接口)
  10. * my-posts : {} 我的文章(登录态)
  11. * publish : { title, content, coverImage?, visibility?, relatedDimensions? }
  12. * 运行:npx playwright test -c playwright.config.api.js --project=desktop
  13. */
  14. const { test, expect } = require('@playwright/test');
  15. const { registerRole, apiPost, uniqueSuffix } = require('../helpers/api');
  16. test.describe('【API级】文章管理全流程', () => {
  17. let ctx;
  18. test.beforeAll(async ({ request }) => {
  19. ctx = await registerRole(request, 'parent', 'QA-ARTICLE');
  20. });
  21. // ===== 场景1:查看文章列表 =====
  22. test('[场景1] 查看文章列表 - 期望返回文章列表', async ({ request }) => {
  23. const r = await apiPost(request, '/api/articles/list', { page: 1, size: 10 }, ctx.token);
  24. expect(r.code).toBe(200);
  25. expect(r.data).toBeTruthy();
  26. expect(Array.isArray(r.data.records)).toBeTruthy();
  27. });
  28. // ===== 场景2:管理文章分类 =====
  29. test('[场景2] 文章分类 - 期望分类列表可用', async ({ request }) => {
  30. const r = await apiPost(request, '/api/articles/categories', {}, ctx.token);
  31. expect(r.code).toBe(200);
  32. expect(Array.isArray(r.data)).toBeTruthy();
  33. });
  34. // ===== 场景3:查看文章详情 =====
  35. test('[场景3] 查看文章详情 - 期望返回文章内容', async ({ request }) => {
  36. const list = await apiPost(request, '/api/articles/list', { page: 1, size: 1 }, ctx.token);
  37. expect(list.code).toBe(200);
  38. const first = (list.data.records || [])[0];
  39. if (!first) {
  40. test.skip(true, '远程暂无已发布文章,跳过详情校验');
  41. return;
  42. }
  43. const r = await apiPost(request, '/api/articles/detail', { id: first.id }, ctx.token);
  44. expect(r.code).toBe(200);
  45. expect(r.data).toBeTruthy();
  46. expect(String(r.data.id)).toBe(String(first.id));
  47. });
  48. // ===== 场景4:我的文章(登录态)=====
  49. test('[场景4] 我的文章 - 期望返回登录用户发布记录', async ({ request }) => {
  50. const r = await apiPost(request, '/api/articles/my-posts', {}, ctx.token);
  51. expect(r.code).toBe(200);
  52. expect(r.data).toBeTruthy();
  53. });
  54. // ===== 场景5:发布接口参数校验(发布→审核→发布链路的前置校验)=====
  55. test('[场景5] 发布文章缺少标题 - 期望返回错误提示', async ({ request }) => {
  56. const r = await apiPost(request, '/api/articles/publish', { content: 'QA' }, ctx.token);
  57. expect(r.code).not.toBe(200);
  58. expect(String(r.message).length).toBeGreaterThan(0);
  59. });
  60. test('[场景5b] 精选文章 - 期望可获取', async ({ request }) => {
  61. const r = await apiPost(request, '/api/articles/featured', {}, ctx.token);
  62. expect(r.code).toBe(200);
  63. expect(r.data).toBeTruthy();
  64. });
  65. // ===== 场景6:发布权限边界(非内容运营角色不应能直接发布)=====
  66. test('[场景6] 家长角色发布文章 - 期望受权限/审核流程约束', async ({ request }) => {
  67. const r = await apiPost(
  68. request,
  69. '/api/articles/publish',
  70. { title: 'QA_' + uniqueSuffix(), content: 'QA content' },
  71. ctx.token
  72. );
  73. // 允许两种合法结果:发布成功(进入待审核)或被权限/审核规则拒绝
  74. // 关键断言:不能是「静默无响应 / 500 内部错误」
  75. expect([200, 500]).toContain(r.code);
  76. expect(String(r.message).length).toBeGreaterThan(0);
  77. });
  78. });