|
15 | 15 |
|
16 | 16 | import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; |
17 | 17 | import type { CueConfig, CueEvent, CueRunResult } from '../../../main/cue/cue-types'; |
18 | | -import type { SessionInfo } from '../../../shared/types'; |
19 | 18 |
|
20 | 19 | // Mock the yaml loader |
21 | 20 | const mockLoadCueConfig = vi.fn<(projectRoot: string) => CueConfig | null>(); |
@@ -53,48 +52,7 @@ import { |
53 | 52 | calculateNextScheduledTime, |
54 | 53 | type CueEngineDeps, |
55 | 54 | } from '../../../main/cue/cue-engine'; |
56 | | - |
57 | | -function createMockSession(overrides: Partial<SessionInfo> = {}): SessionInfo { |
58 | | - return { |
59 | | - id: 'session-1', |
60 | | - name: 'Test Session', |
61 | | - toolType: 'claude-code', |
62 | | - cwd: '/projects/test', |
63 | | - projectRoot: '/projects/test', |
64 | | - ...overrides, |
65 | | - }; |
66 | | -} |
67 | | - |
68 | | -function createMockConfig(overrides: Partial<CueConfig> = {}): CueConfig { |
69 | | - return { |
70 | | - subscriptions: [], |
71 | | - settings: { timeout_minutes: 30, timeout_on_fail: 'break', max_concurrent: 1, queue_size: 10 }, |
72 | | - ...overrides, |
73 | | - }; |
74 | | -} |
75 | | - |
76 | | -function createMockDeps(overrides: Partial<CueEngineDeps> = {}): CueEngineDeps { |
77 | | - return { |
78 | | - getSessions: vi.fn(() => [createMockSession()]), |
79 | | - onCueRun: vi.fn(async (request: Parameters<CueEngineDeps['onCueRun']>[0]) => ({ |
80 | | - runId: 'run-1', |
81 | | - sessionId: 'session-1', |
82 | | - sessionName: 'Test Session', |
83 | | - subscriptionName: request.subscriptionName, |
84 | | - event: request.event, |
85 | | - status: 'completed' as const, |
86 | | - stdout: 'output', |
87 | | - stderr: '', |
88 | | - exitCode: 0, |
89 | | - durationMs: 100, |
90 | | - startedAt: new Date().toISOString(), |
91 | | - endedAt: new Date().toISOString(), |
92 | | - })), |
93 | | - onStopCueRun: vi.fn(() => true), |
94 | | - onLog: vi.fn(), |
95 | | - ...overrides, |
96 | | - }; |
97 | | -} |
| 55 | +import { createMockSession, createMockConfig, createMockDeps } from './cue-test-helpers'; |
98 | 56 |
|
99 | 57 | describe('CueEngine', () => { |
100 | 58 | let yamlWatcherCleanup: ReturnType<typeof vi.fn>; |
@@ -930,6 +888,39 @@ describe('CueEngine', () => { |
930 | 888 | engine.stop(); |
931 | 889 | }); |
932 | 890 |
|
| 891 | + it('stopRun adds the stopped run to the activity log', async () => { |
| 892 | + const deps = createMockDeps({ |
| 893 | + onCueRun: vi.fn(() => new Promise<CueRunResult>(() => {})), |
| 894 | + }); |
| 895 | + const config = createMockConfig({ |
| 896 | + subscriptions: [ |
| 897 | + { |
| 898 | + name: 'timer', |
| 899 | + event: 'time.heartbeat', |
| 900 | + enabled: true, |
| 901 | + prompt: 'test', |
| 902 | + interval_minutes: 60, |
| 903 | + }, |
| 904 | + ], |
| 905 | + }); |
| 906 | + mockLoadCueConfig.mockReturnValue(config); |
| 907 | + const engine = new CueEngine(deps); |
| 908 | + engine.start(); |
| 909 | + |
| 910 | + await vi.advanceTimersByTimeAsync(10); |
| 911 | + |
| 912 | + const activeRun = engine.getActiveRuns()[0]; |
| 913 | + expect(activeRun).toBeDefined(); |
| 914 | + engine.stopRun(activeRun.runId); |
| 915 | + |
| 916 | + const log = engine.getActivityLog(); |
| 917 | + expect(log).toHaveLength(1); |
| 918 | + expect(log[0].runId).toBe(activeRun.runId); |
| 919 | + expect(log[0].status).toBe('stopped'); |
| 920 | + |
| 921 | + engine.stop(); |
| 922 | + }); |
| 923 | + |
933 | 924 | it('stopAll clears all active runs', async () => { |
934 | 925 | // Use a slow-resolving onCueRun to keep runs active |
935 | 926 | const deps = createMockDeps({ |
@@ -1897,6 +1888,47 @@ describe('CueEngine', () => { |
1897 | 1888 | engine.stop(); |
1898 | 1889 | }); |
1899 | 1890 |
|
| 1891 | + it('refreshes nextTriggers after time.scheduled fires', async () => { |
| 1892 | + // Monday 2026-03-09 at 08:59 — next trigger should be 09:00 today |
| 1893 | + vi.setSystemTime(new Date('2026-03-09T08:59:00')); |
| 1894 | + |
| 1895 | + const config = createMockConfig({ |
| 1896 | + subscriptions: [ |
| 1897 | + { |
| 1898 | + name: 'refresh-schedule', |
| 1899 | + event: 'time.scheduled', |
| 1900 | + enabled: true, |
| 1901 | + prompt: 'check', |
| 1902 | + schedule_times: ['09:00'], |
| 1903 | + }, |
| 1904 | + ], |
| 1905 | + }); |
| 1906 | + mockLoadCueConfig.mockReturnValue(config); |
| 1907 | + const deps = createMockDeps(); |
| 1908 | + const engine = new CueEngine(deps); |
| 1909 | + engine.start(); |
| 1910 | + |
| 1911 | + const statusBefore = engine.getStatus(); |
| 1912 | + const subBefore = statusBefore.find((s) => s.sessionId === 'session-1'); |
| 1913 | + const nextBefore = subBefore!.nextTrigger!; |
| 1914 | + // nextTrigger should be pointing at 09:00 today (ISO string) |
| 1915 | + const nextBeforeDate = new Date(nextBefore); |
| 1916 | + expect(nextBeforeDate.getHours()).toBe(9); |
| 1917 | + expect(nextBeforeDate.getMinutes()).toBe(0); |
| 1918 | + |
| 1919 | + // Advance to 09:00 — the subscription fires |
| 1920 | + vi.advanceTimersByTime(60_000); |
| 1921 | + await vi.advanceTimersByTimeAsync(10); |
| 1922 | + |
| 1923 | + // After firing, nextTrigger should have advanced to a future time (tomorrow 09:00) |
| 1924 | + const statusAfter = engine.getStatus(); |
| 1925 | + const subAfter = statusAfter.find((s) => s.sessionId === 'session-1'); |
| 1926 | + expect(subAfter!.nextTrigger).toBeDefined(); |
| 1927 | + expect(new Date(subAfter!.nextTrigger!).getTime()).toBeGreaterThan(nextBeforeDate.getTime()); |
| 1928 | + |
| 1929 | + engine.stop(); |
| 1930 | + }); |
| 1931 | + |
1900 | 1932 | it('uses prompt_file when configured', async () => { |
1901 | 1933 | // Monday at 08:59 — fires at 09:00 |
1902 | 1934 | vi.setSystemTime(new Date('2026-03-09T08:59:00')); |
|
0 commit comments