• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

VaclavObornik / mongodash / 24607504909

18 Apr 2026 03:09PM UTC coverage: 91.218% (+0.09%) from 91.133%
24607504909

push

github

VaclavObornik
fix: address Copilot review round 11

1. waitUntilReactiveTasksIdle (whitelist mode) now always checks the
   planner buffer. Skipping it entirely let the helper return idle
   before an in-flight change event had a chance to be turned into
   the task record the DB check looks for. Only the worker count
   check remains global-scope-skipped so unrelated tests' worker
   pools cannot block a scoped wait.

2. docs/reactive-tasks/testing.md now notes the narrow race where
   triggering a write and immediately calling the scoped wait can
   return early if stabilityDurationMs is too short, with a
   recommended workaround (raise stabilityDurationMs or pre-poll
   for the task record).

3. cronTasksConcurrency 'no polls after stop' assertion tightened:
   stopCronTasks is fire-and-forget, so a worker already inside
   findOneAndUpdate at the time could still tick the spy. Capture
   the poll count after a 200ms grace period and assert it does
   not grow over a full 5s back-off window, instead of requiring
   spy.called === false outright.

1492 of 1748 branches covered (85.35%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

54 existing lines in 9 files now uncovered.

2320 of 2431 relevant lines covered (95.43%)

373.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.79
/src/testing/assertNoReactiveTaskErrors.ts
1
import { Filter } from 'mongodb';
2
import { ReactiveTaskRecord, ReactiveTaskScheduler, _scheduler } from '../reactiveTasks';
261✔
3
import { resolveWhitelistFilter, WhitelistRule } from './resolveWhitelistFilter';
261✔
4

5
export interface AssertNoReactiveTaskErrorsOptions {
6
    /**
7
     * Check for errors occurring after this time.
8
     * Essential to isolate the current test run.
9
     */
10
    since: Date;
11

12
    /**
13
     * Optional: Check only tasks related to specific entities.
14
     * If provided, errors in collections/tasks not matching the whitelist are ignored.
15
     */
16
    whitelist?: WhitelistRule[];
17

18
    /**
19
     * Optional: Whitelist specific errors.
20
     * If a string is provided, exact match is required.
21
     * If a RegExp is provided, it must test true against the error message.
22
     */
23
    excludeErrors?: (string | RegExp)[];
24

25
    /**
26
     * Optional: ReactiveTaskScheduler instance to use.
27
     * Essential for isolated testing where multiple schedulers might exist.
28
     */
29
    scheduler?: ReactiveTaskScheduler;
30
}
31

32
/**
33
 * Asserts that no reactive tasks have failed during the test run.
34
 * Checks the 'executionHistory' and 'lastError' of tasks in all registered collections.
35
 */
36
export async function assertNoReactiveTaskErrors(options: AssertNoReactiveTaskErrorsOptions): Promise<void> {
261✔
37
    let registry;
38

39
    // Fallback or override logic
40
    const schedulerToUse = options.scheduler || _scheduler;
7✔
41
    registry = schedulerToUse.getRegistry();
7✔
42

43
    const entries = registry.getAllEntries();
7✔
44

45
    const errorsFound: Array<{
46
        task: string;
47
        sourceDocId: unknown;
48
        error: string;
49
        at: Date;
50
    }> = [];
7✔
51

52
    const hasWhitelist = options.whitelist && options.whitelist.length > 0;
7✔
53

54
    for (const entry of entries) {
7✔
55
        // If whitelist is active, check if this collection is relevant
56
        let whitelistFilter: Filter<ReactiveTaskRecord> | null = null;
7✔
57
        if (hasWhitelist) {
7✔
58
            const resolution = await resolveWhitelistFilter(options.whitelist!, entry.sourceCollection);
5✔
59
            if (resolution === 'skip') continue;
5✔
60
            if (resolution !== 'matchAll') {
4!
61
                whitelistFilter = resolution;
4✔
62
            }
63
        }
64

65
        // Build independent query for each collection
66
        const baseQuery: Filter<ReactiveTaskRecord> = {
6✔
67
            $or: [{ 'executionHistory.status': 'failed', 'executionHistory.at': { $gte: options.since } }],
68
        };
69

70
        const query = whitelistFilter ? { $and: [baseQuery, whitelistFilter] } : baseQuery;
6✔
71

72
        const tasksWithHistory = await entry.tasksCollection.find(query).toArray();
6✔
73

74
        for (const taskRecord of tasksWithHistory) {
6✔
75
            if (!taskRecord.executionHistory) continue;
5!
76

77
            for (const item of taskRecord.executionHistory) {
5✔
78
                // 1. Check Date
79
                if (item.at < options.since) continue;
5!
80
                // 2. Check Status
81
                if (item.status !== 'failed') continue;
5!
82

83
                const errorMessage = item.error || 'Unknown error';
5!
84

85
                // 3. Check Whitelist (excludeErrors)
86
                let isExcluded = false;
5✔
87
                if (options.excludeErrors) {
5✔
88
                    for (const pattern of options.excludeErrors) {
3✔
89
                        if (typeof pattern === 'string') {
5✔
90
                            if (pattern === errorMessage) {
3✔
91
                                isExcluded = true;
1✔
92
                                break;
1✔
93
                            }
94
                        } else if (pattern instanceof RegExp) {
2!
95
                            if (pattern.test(errorMessage)) {
2✔
96
                                isExcluded = true;
1✔
97
                                break;
1✔
98
                            }
99
                        }
100
                    }
101
                }
102

103
                if (!isExcluded) {
5✔
104
                    errorsFound.push({
3✔
105
                        task: taskRecord.task,
106
                        sourceDocId: taskRecord.sourceDocId,
107
                        error: errorMessage,
108
                        at: item.at,
109
                    });
110
                }
111
            }
112
        }
113
    }
114

115
    if (errorsFound.length > 0) {
7✔
116
        const errorDetails = errorsFound
3✔
UNCOV
117
            .sort((a, b) => a.at.getTime() - b.at.getTime())
×
118
            .map((e) => `[${e.at.toISOString()}] Task '${e.task}' (Doc: ${e.sourceDocId}): ${e.error}`)
3✔
119
            .join('\n');
120

121
        throw new Error(`Found ${errorsFound.length} unexpected reactive task errors:\n${errorDetails}`);
3✔
122
    }
123
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc