ai-analysis.service.spec.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. import { firstValueFrom, of } from 'rxjs';
  2. import { filter, timeout } from 'rxjs/operators';
  3. import { AiAnalysisService, AiStreamState } from './ai-analysis.service';
  4. import { CloudApiService } from '../../../app/core/services/cloud-api.service';
  5. describe('AiAnalysisService retry lifecycle', () => {
  6. it('keeps the outer stream alive while a transient failure is retried', async () => {
  7. const promptConfig = {
  8. ensureLoaded: () => of([]),
  9. getTextSnapshot: (_key: string, fallback: string) => fallback,
  10. getTextForDefaultTemplate: (template: string) => template,
  11. getPromptKeyForTemplateText: () => undefined,
  12. getDefaultAiModel: () => 'deepseek-v4-flash',
  13. getOutputStyleInstruction: () => '',
  14. };
  15. const cloud = jasmine.createSpyObj<CloudApiService>('CloudApiService', ['runPromise']);
  16. cloud.runPromise.and.returnValues(Promise.reject(new Error('temporary upstream failure')), Promise.resolve({ choices: [{ message: { content: '重试成功' }, finish_reason: 'stop' }] }));
  17. const service = new AiAnalysisService(promptConfig as any, cloud);
  18. const finalState = await firstValueFrom(service.streamAnalysis('分析真实评论').pipe(
  19. filter((state: AiStreamState) => state.complete && !state.error),
  20. timeout(2_000),
  21. ));
  22. expect(cloud.runPromise).toHaveBeenCalledTimes(2);
  23. expect(finalState.text).toBe('重试成功');
  24. });
  25. });
  26. describe('AiAnalysisService extractAiText reasoning isolation', () => {
  27. let service: AiAnalysisService;
  28. beforeEach(() => {
  29. const promptConfig = {
  30. ensureLoaded: () => of([]),
  31. getTextSnapshot: (_key: string, fallback: string) => fallback,
  32. getTextForDefaultTemplate: (template: string) => template,
  33. getPromptKeyForTemplateText: () => undefined,
  34. getDefaultAiModel: () => 'deepseek-v4-flash',
  35. getOutputStyleInstruction: () => '',
  36. };
  37. service = new AiAnalysisService(promptConfig as any, jasmine.createSpyObj<CloudApiService>('CloudApiService', ['runPromise']));
  38. });
  39. it('only extracts content when both content and reasoning_content are present', () => {
  40. const json = {
  41. choices: [{
  42. delta: {
  43. content: '正式内容',
  44. reasoning_content: '{"type":"metric-grid","metrics":[残缺草稿',
  45. },
  46. }],
  47. };
  48. expect((service as any).extractAiText(json)).toBe('正式内容');
  49. });
  50. it('returns an empty string when only reasoning_content exists (no content fallback)', () => {
  51. const json = {
  52. choices: [{ delta: { reasoning_content: '残缺JSON草稿,无正式内容' } }],
  53. };
  54. expect((service as any).extractAiText(json)).toBe('');
  55. });
  56. });