Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1import { type Dependencies } from '../../iocContainer/index.js';
2import { type MockedFn } from '../../test/mockHelpers/jestMocks.js';
3import makeGetRuleAnomalyDetectionStatistics from './getRuleAnomalyDetectionStatistics.js';
4
5/**
6 * @fileoverview The testing strategy here is to just snapshot the queries
7 * that our service function is generating, and run those manually to verify
8 * that they work. If we update the warehouse structure, we can update the
9 * snapshots and manually re-run the new queries. But having these snapshots at
10 * least makes sure we can't change inadvertently change the generated queries.
11 */
12describe('getRuleAnomalyDetectionStatistics', () => {
13 let queryMock: MockedFn<
14 (
15 query: string,
16 tracer: any,
17 binds?: readonly unknown[],
18 ) => Promise<unknown[]>
19 >;
20 let getRulePassStatistics: Dependencies['getRuleAnomalyDetectionStatistics'];
21
22 beforeAll(() => {
23 const queryResult = [
24 {
25 RULE_ID: 'a',
26 NUM_PASSES: 2,
27 NUM_DISTINCT_USERS: 1,
28 NUM_RUNS: 4,
29 TS_START_INCLUSIVE: '2022-05-07 23:00:00.00',
30 RULE_VERSION: '2022-05-01T12:10:00.00305Z',
31 },
32 ];
33
34 // Scope of this is just the test suite, so mutation should be ok.
35
36 queryMock = jest.fn() as any;
37 queryMock.mockResolvedValue(queryResult);
38
39 const dataWarehouseMock = {
40 query: queryMock,
41 transaction: jest.fn(),
42 start: jest.fn(),
43 close: jest.fn(),
44 getProvider: () => 'clickhouse' as const,
45 };
46
47 // Scope of this is just the test suite, so mutation should be ok.
48
49 getRulePassStatistics = makeGetRuleAnomalyDetectionStatistics(
50 dataWarehouseMock,
51 {} as any,
52 );
53 });
54
55 beforeEach(() => {
56 // This is only safe while we're not running tests concurrently.
57 // Consider using the `makeTestWithFixture` helper instead to make
58 // a local copy of this state for each test.
59 queryMock.mockClear();
60 });
61
62 test('should return the result from the warehouse, properly formatted', async () => {
63 const result = await getRulePassStatistics();
64 expect(result).toEqual([
65 {
66 ruleId: 'a',
67 passCount: 2,
68 runsCount: 4,
69 passingUsersCount: 1,
70 windowStart: new Date('2022-05-07 23:00:00.00'),
71 approxRuleVersion: new Date('2022-05-01T12:10:00.00305Z'),
72 },
73 ]);
74 });
75
76 test('should generate the proper query when given no filters', async () => {
77 await getRulePassStatistics();
78
79 expect(queryMock).toHaveBeenCalledTimes(1);
80 expect(queryMock.mock.calls[0]).toMatchInlineSnapshot(`
81 [
82 "
83 SELECT
84 rule_id,
85 rule_version,
86 num_passes,
87 num_runs,
88 array_size(passes_distinct_user_ids) as num_distinct_users,
89 ts_start_inclusive
90 FROM RULE_ANOMALY_DETECTION_SERVICE.RULE_EXECUTION_STATISTICS
91 WHERE ts_end_exclusive <= SYSDATE()
92 ORDER BY ts_start_inclusive DESC;",
93 {},
94 [],
95 ]
96 `);
97 });
98
99 test('should generate the proper query when given a start time filter', async () => {
100 await getRulePassStatistics({ startTime: new Date('2022-05-02') });
101
102 expect(queryMock).toHaveBeenCalledTimes(1);
103 expect(queryMock.mock.calls[0]).toMatchInlineSnapshot(`
104 [
105 "
106 SELECT
107 rule_id,
108 rule_version,
109 num_passes,
110 num_runs,
111 array_size(passes_distinct_user_ids) as num_distinct_users,
112 ts_start_inclusive
113 FROM RULE_ANOMALY_DETECTION_SERVICE.RULE_EXECUTION_STATISTICS
114 WHERE ts_end_exclusive <= SYSDATE() AND ts_start_inclusive >= ?
115 ORDER BY ts_start_inclusive DESC;",
116 {},
117 [
118 2022-05-02T00:00:00.000Z,
119 ],
120 ]
121 `);
122 });
123
124 test('should generate the proper query when given a ruleIds filter', async () => {
125 await getRulePassStatistics({ ruleIds: ['1', '2'] });
126
127 expect(queryMock).toHaveBeenCalledTimes(1);
128 expect(queryMock.mock.calls[0]).toMatchInlineSnapshot(`
129 [
130 "
131 SELECT
132 rule_id,
133 rule_version,
134 num_passes,
135 num_runs,
136 array_size(passes_distinct_user_ids) as num_distinct_users,
137 ts_start_inclusive
138 FROM RULE_ANOMALY_DETECTION_SERVICE.RULE_EXECUTION_STATISTICS
139 WHERE ts_end_exclusive <= SYSDATE() AND rule_id IN (?,?)
140 ORDER BY ts_start_inclusive DESC;",
141 {},
142 [
143 "1",
144 "2",
145 ],
146 ]
147 `);
148 await getRulePassStatistics({ ruleIds: ['1'] });
149
150 expect(queryMock).toHaveBeenCalledTimes(2);
151 expect(queryMock.mock.calls[1]).toMatchInlineSnapshot(`
152 [
153 "
154 SELECT
155 rule_id,
156 rule_version,
157 num_passes,
158 num_runs,
159 array_size(passes_distinct_user_ids) as num_distinct_users,
160 ts_start_inclusive
161 FROM RULE_ANOMALY_DETECTION_SERVICE.RULE_EXECUTION_STATISTICS
162 WHERE ts_end_exclusive <= SYSDATE() AND rule_id IN (?)
163 ORDER BY ts_start_inclusive DESC;",
164 {},
165 [
166 "1",
167 ],
168 ]
169 `);
170 });
171
172 test('should generate the proper query when given both filters', async () => {
173 await getRulePassStatistics({
174 ruleIds: ['1', '2'],
175 startTime: new Date('2022-05-02 23:00:00.00-04'),
176 });
177
178 expect(queryMock).toHaveBeenCalledTimes(1);
179 expect(queryMock.mock.calls[0]).toMatchInlineSnapshot(`
180 [
181 "
182 SELECT
183 rule_id,
184 rule_version,
185 num_passes,
186 num_runs,
187 array_size(passes_distinct_user_ids) as num_distinct_users,
188 ts_start_inclusive
189 FROM RULE_ANOMALY_DETECTION_SERVICE.RULE_EXECUTION_STATISTICS
190 WHERE ts_end_exclusive <= SYSDATE() AND ts_start_inclusive >= ? AND rule_id IN (?,?)
191 ORDER BY ts_start_inclusive DESC;",
192 {},
193 [
194 2022-05-03T03:00:00.000Z,
195 "1",
196 "2",
197 ],
198 ]
199 `);
200 });
201});