api.service.spec.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { provideHttpClient } from '@angular/common/http';
  2. import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
  3. import { TestBed } from '@angular/core/testing';
  4. import { API_CONFIG } from './app.constants';
  5. import { ApiError, ApiService } from './api.service';
  6. import { SessionService } from './session.service';
  7. describe('ApiService', () => {
  8. let api: ApiService;
  9. let http: HttpTestingController;
  10. let sessionToken = '';
  11. beforeEach(() => {
  12. sessionToken = '';
  13. TestBed.configureTestingModule({ providers: [provideHttpClient(), provideHttpClientTesting(), { provide: SessionService, useValue: { token: () => sessionToken } }] });
  14. api = TestBed.inject(ApiService);
  15. http = TestBed.inject(HttpTestingController);
  16. });
  17. afterEach(() => http.verify());
  18. it('routes migrated actions through the cloud gateway and parses JSON result strings', () => {
  19. let actual: unknown;
  20. api.get('node_list', { cpage: 1 }).subscribe((response) => actual = response.result);
  21. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  22. expect(request.request.method).toBe('POST');
  23. expect(request.request.headers.get('X-Parse-Application-Id')).toBe(API_CONFIG.parseAppId);
  24. expect(request.request.body).toEqual({ params: { action: 'node_list', cpage: 1 } });
  25. request.flush({ retcode: 0, result: '[{"ID":1}]' });
  26. expect(actual).toEqual([{ ID: 1 }]);
  27. });
  28. it('keeps not-yet-migrated actions on the legacy gateway', () => {
  29. api.get('order_list', { cpage: 2 }).subscribe();
  30. const request = http.expectOne((candidate) => candidate.url.includes(API_CONFIG.endpoint) && candidate.urlWithParams.includes('action=order_list'));
  31. expect(request.request.method).toBe('GET');
  32. expect(request.request.params.get('cpage')).toBe('2');
  33. request.flush({ retcode: 0, result: [] });
  34. });
  35. it('nests reserved id parameters inside params', () => {
  36. api.get('node_get', { id: 81 }).subscribe();
  37. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  38. expect(request.request.body).toEqual({ params: { action: 'node_get', id: 81 } });
  39. request.flush({ retcode: 0, result: { id: 81 } });
  40. });
  41. it('keeps private addon content without a Parse session and product queries on the legacy gateway', () => {
  42. api.get('content_list', { modelId: 56, myfield2: 'UserId=42' }).subscribe();
  43. api.get('product_list', { cpage: 1 }).subscribe();
  44. const content = http.expectOne((candidate) => candidate.urlWithParams.includes('action=content_list'));
  45. const products = http.expectOne((candidate) => candidate.urlWithParams.includes('action=product_list'));
  46. expect(content.request.url).toContain(API_CONFIG.endpoint);
  47. expect(products.request.url).toContain(API_CONFIG.endpoint);
  48. content.flush({ retcode: 0, result: [] });
  49. products.flush({ retcode: 0, result: [] });
  50. });
  51. it('routes supported private addon content with a Parse session', () => {
  52. sessionToken = 'r:session-token';
  53. api.get('content_list', { modelId: 56, myfield2: 'UserId=42|dqrq=20260818' }).subscribe();
  54. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  55. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'content_list', modelId: 56, myfield2: 'UserId=42|dqrq=20260818' } });
  56. request.flush({ retcode: 0, result: [] });
  57. });
  58. it('routes verified learning detail reads with a Parse session', () => {
  59. sessionToken = 'r:session-token';
  60. api.get('e_record_detail', { id: 931 }).subscribe();
  61. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  62. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_record_detail', id: 931 } });
  63. request.flush({ retcode: 0, result: [{ GeneralID: 149, detail: [{ Title: 'beautiful' }] }] });
  64. });
  65. it('routes course word progress reads with a Parse session', () => {
  66. sessionToken = 'r:session-token';
  67. api.get('e_ck_list', { uid: 58, nids: '40,41' }).subscribe();
  68. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  69. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_ck_list', uid: 58, nids: '40,41' } });
  70. request.flush({ retcode: 0, result: [{ GeneralID: 2505, w_learned: 7 }] });
  71. });
  72. it('routes team member, team statistics, and coach student reads with a Parse session', () => {
  73. sessionToken = 'r:session-token';
  74. api.get('user_list', { puid: 58, cpage: 1 }).subscribe();
  75. api.get('user_dept', { uid: 58 }).subscribe();
  76. api.get('e_user_list', { cpage: 1 }).subscribe();
  77. const [members, statistics, students] = http.match(API_CONFIG.cloudFunctionUrl);
  78. expect(members.request.body).toEqual({ token: 'r:session-token', params: { action: 'user_list', puid: 58, cpage: 1 } });
  79. expect(statistics.request.body).toEqual({ token: 'r:session-token', params: { action: 'user_dept', uid: 58 } });
  80. expect(students.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_user_list', cpage: 1 } });
  81. members.flush({ retcode: 0, result: [] });
  82. statistics.flush({ retcode: 0, result: { childs: 0, v0: 0 } });
  83. students.flush({ retcode: 0, result: [] });
  84. });
  85. it('routes typed virtual-currency history reads with a Parse session', () => {
  86. sessionToken = 'r:session-token';
  87. api.get('user_point_list', { uid: 58, stype: 3, cpage: 1 }).subscribe();
  88. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  89. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'user_point_list', uid: 58, stype: 3, cpage: 1 } });
  90. request.flush({ retcode: 0, result: [{ ExpHisID: 1, score: 2 }] });
  91. });
  92. it('routes legacy model-based feedback submissions with a Parse session', () => {
  93. sessionToken = 'r:session-token';
  94. const model = JSON.stringify({ UserID: 58, Title: '小树 反馈的意见', TContent: '请假一天', Cateid: 22 });
  95. api.post('guestbook_add', { model }).subscribe();
  96. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  97. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'guestbook_add', model } });
  98. request.flush({ retcode: 0, result: { GID: 1, TContent: '请假一天' } });
  99. });
  100. it('routes learning word state writes with a Parse session', () => {
  101. sessionToken = 'r:session-token';
  102. api.post('e_add_words', { uid: 58, wordsId: '2505,2503', kcid: 16, ifnew: 1 }).subscribe();
  103. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  104. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_add_words', uid: 58, wordsId: '2505,2503', kcid: 16, ifnew: 1 } });
  105. request.flush({ retcode: 0, result: { affected: 2, newWordState: 1 } });
  106. });
  107. it('routes study record upserts with a Parse session', () => {
  108. sessionToken = 'r:session-token';
  109. api.post('stu_record_update_v2', { orderId: 918, uid: 23, addon: '{"xxqs":[]}' }).subscribe();
  110. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  111. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'stu_record_update_v2', orderId: 918, uid: 23, addon: '{"xxqs":[]}' } });
  112. request.flush({ retcode: 0, result: { GeneralID: 123456, created: true } });
  113. });
  114. it('routes scheduled study record creation with a Parse session', () => {
  115. sessionToken = 'r:session-token';
  116. api.post('content_add_zt', { content: '{"ModelID":56}', addon: '{"UserID":23,"xxqs":"[]"}' }).subscribe();
  117. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  118. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'content_add_zt', content: '{"ModelID":56}', addon: '{"UserID":23,"xxqs":"[]"}' } });
  119. request.flush({ retcode: 0, result: '123456' });
  120. });
  121. it('routes normalized content updates with a Parse session', () => {
  122. sessionToken = 'r:session-token';
  123. api.post('content_update', { content: '{"GeneralID":123456}', addon: '{"learned":2}' }).subscribe();
  124. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  125. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'content_update', content: '{"GeneralID":123456}', addon: '{"learned":2}' } });
  126. request.flush({ retcode: 0, result: '123456' });
  127. });
  128. it('routes normalized content creation with a Parse session', () => {
  129. sessionToken = 'r:session-token';
  130. api.post('content_add', { content: '{"ModelID":61,"nodeId":389}', addon: '{"UserID":23,"df":8}' }).subscribe();
  131. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  132. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'content_add', content: '{"ModelID":61,"nodeId":389}', addon: '{"UserID":23,"df":8}' } });
  133. request.flush({ retcode: 0, result: '123456' });
  134. });
  135. it('routes appointment statistics with a Parse session', () => {
  136. sessionToken = 'r:session-token';
  137. api.post('e_order_tongji', { uid: 23 }).subscribe();
  138. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  139. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_order_tongji', uid: 23 } });
  140. request.flush({ retcode: 0, result: { t30: '1', t_shichang: '0.5', t_total: '1' } });
  141. });
  142. it('routes complete appointment transitions with a Parse session', () => {
  143. sessionToken = 'r:session-token';
  144. api.post('e_order_update_v2', { uid: 23, status: 11, content: '{"GeneralID":918}' }).subscribe();
  145. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  146. expect(request.request.body).toEqual({ token: 'r:session-token', params: { action: 'e_order_update_v2', uid: 23, status: 11, content: '{"GeneralID":918}' } });
  147. request.flush({ retcode: 0, result: { GeneralID: '918', status: 11, reviewCount: 15, periodDeducted: true } });
  148. });
  149. it('keeps learning detail reads on legacy until a real Parse session exists', () => {
  150. sessionToken = 'legacy-session';
  151. api.get('e_words_list', { uid: 58 }).subscribe();
  152. const request = http.expectOne((candidate) => candidate.urlWithParams.includes('action=e_words_list'));
  153. expect(request.request.url).toContain(API_CONFIG.endpoint);
  154. request.flush({ retcode: 0, result: [] });
  155. });
  156. it('routes public vocabulary addon content without a session', () => {
  157. api.get('content_list', { modelId: 52, nid: 327 }).subscribe();
  158. const request = http.expectOne(API_CONFIG.cloudFunctionUrl);
  159. expect(request.request.body).toEqual({ params: { action: 'content_list', modelId: 52, nid: 327 } });
  160. request.flush({ retcode: 0, result: [] });
  161. });
  162. it('turns retcode -1 responses into ApiError', () => {
  163. let actual: unknown;
  164. api.post('user_login_passwd', {}).subscribe({ error: (error) => actual = error });
  165. http.expectOne(API_CONFIG.cloudFunctionUrl).flush({ retcode: -1, retmsg: '账号或密码错误', result: null }, { status: 401, statusText: 'Unauthorized' });
  166. http.expectOne((candidate) => candidate.urlWithParams.includes('action=user_login_passwd')).flush({ retcode: -1, retmsg: '登录失败', result: null });
  167. expect(actual instanceof ApiError).toBeTrue();
  168. expect((actual as ApiError).message).toBe('登录失败');
  169. });
  170. it('keeps a successful password login on the cloud response', () => {
  171. let actual: unknown;
  172. api.post('user_login_passwd', { name: 'tree', passwd: 'password' }).subscribe((response) => actual = response.result);
  173. http.expectOne(API_CONFIG.cloudFunctionUrl).flush({ retcode: 0, result: { userId: 42, sessionToken: 'r:token' } });
  174. http.expectNone((candidate) => candidate.url.includes(API_CONFIG.endpoint));
  175. expect(actual).toEqual({ userId: 42, sessionToken: 'r:token' });
  176. });
  177. });