Skip to content

Commit 72d6d68

Browse files
committed
🧪 test(app): close coverage gaps across store, triggers, watchers, and api
- Add regression tests for store/update-operation, internal-self-update, self-update-operation, self-update-controller, self-update-finalize, update-runtime-context, request-update, Trigger digest/batch buffers, Docker lifecycle rollback, docker-helpers, AgentClient, container handlers/list, container-actions, webhook, trigger, and coverage provider shared helpers - Drop unreachable null guards in store/update-operation and trailing throws in vitest coverage-provider shared read/write helpers - Include api/internal-self-update.ts in the core-api-routes mutation mutate set so Stryker exercises the new endpoint
1 parent 858dfe2 commit 72d6d68

22 files changed

+3032
-18
lines changed

‎.github/workflows/quality-mutation-monthly.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
package: app
6666
incremental_file: reports/stryker-incremental-app-core-api-routes.json
6767
mutate: >-
68-
api/agent.ts,api/api.ts,api/app.ts,api/audit*.ts,api/backup.ts,api/component.ts,api/debug.ts,api/docker-trigger.ts,api/error-response.ts,api/group.ts,api/header-value.ts,api/health.ts,api/helpers.ts,api/icons.ts,api/icons/**/*.ts,api/index.ts,api/log.ts,api/notification.ts,api/pagination-links.ts,api/preview.ts,api/prometheus.ts,api/rate-limit-key.ts,api/registry.ts,api/server.ts,api/settings.ts,api/store.ts,api/trigger.ts,api/ui.ts,api/watcher.ts,!**/*.d.ts,!**/*.test.ts,!**/*.fuzz.test.ts,!**/*.typecheck.ts,!test/**,!dist/**,!coverage/**
68+
api/agent.ts,api/api.ts,api/app.ts,api/audit*.ts,api/backup.ts,api/component.ts,api/debug.ts,api/docker-trigger.ts,api/error-response.ts,api/group.ts,api/header-value.ts,api/health.ts,api/helpers.ts,api/icons.ts,api/icons/**/*.ts,api/index.ts,api/internal-self-update.ts,api/log.ts,api/notification.ts,api/pagination-links.ts,api/preview.ts,api/prometheus.ts,api/rate-limit-key.ts,api/registry.ts,api/server.ts,api/settings.ts,api/store.ts,api/trigger.ts,api/ui.ts,api/watcher.ts,!**/*.d.ts,!**/*.test.ts,!**/*.fuzz.test.ts,!**/*.typecheck.ts,!test/**,!dist/**,!coverage/**
6969
- name: app-core-domain-support
7070
package: app
7171
incremental_file: reports/stryker-incremental-app-core-domain-support.json

‎app/agent/AgentClient.test.ts‎

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,6 +1159,228 @@ describe('AgentClient', () => {
11591159
expect(storeContainer.deleteContainer).toHaveBeenCalledWith('c1');
11601160
});
11611161

1162+
test('should ignore watcher-cycle cleanup for invalid container ids', () => {
1163+
(client as any).pendingWatcherCycleReports.set(
1164+
'watcher',
1165+
new Map([
1166+
[
1167+
'c1',
1168+
{
1169+
container: {
1170+
id: 'c1',
1171+
name: 'test',
1172+
watcher: 'watcher',
1173+
},
1174+
changed: true,
1175+
},
1176+
],
1177+
]),
1178+
);
1179+
1180+
(client as any).clearPendingWatcherCycleReportByContainerId('');
1181+
1182+
expect((client as any).pendingWatcherCycleReports.get('watcher')?.has('c1')).toBe(true);
1183+
});
1184+
1185+
test('should clear watcher-cycle reports when the last container in a watcher is removed', () => {
1186+
(client as any).pendingWatcherCycleReports.set(
1187+
'watcher',
1188+
new Map([
1189+
[
1190+
'c1',
1191+
{
1192+
container: {
1193+
id: 'c1',
1194+
name: 'test',
1195+
watcher: 'watcher',
1196+
},
1197+
changed: true,
1198+
},
1199+
],
1200+
]),
1201+
);
1202+
1203+
(client as any).clearPendingWatcherCycleReportByContainerId('c1');
1204+
1205+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(false);
1206+
});
1207+
1208+
test('should ignore watcher-cycle reports that do not have a resolvable container key', () => {
1209+
const beforeSize = (client as any).pendingWatcherCycleReports.size;
1210+
1211+
(client as any).rememberPendingWatcherCycleReport({
1212+
container: {
1213+
watcher: 'watcher',
1214+
},
1215+
changed: true,
1216+
});
1217+
1218+
expect((client as any).pendingWatcherCycleReports.size).toBe(beforeSize);
1219+
});
1220+
1221+
test('should ignore invalid watcher-cycle lookups before taking a pending report', () => {
1222+
const report = {
1223+
container: {
1224+
id: 'c1',
1225+
name: 'test',
1226+
watcher: 'watcher',
1227+
},
1228+
changed: true,
1229+
};
1230+
(client as any).pendingWatcherCycleReports.set('watcher', new Map([['c1', report]]));
1231+
1232+
expect((client as any).takePendingWatcherCycleReport('', report.container)).toBeUndefined();
1233+
expect(
1234+
(client as any).takePendingWatcherCycleReport('watcher', { watcher: 'watcher' } as any),
1235+
).toBeUndefined();
1236+
expect(
1237+
(client as any).takePendingWatcherCycleReport('watcher', {
1238+
...report.container,
1239+
id: 'missing',
1240+
}),
1241+
).toBeUndefined();
1242+
expect((client as any).takePendingWatcherCycleReport('watcher', report.container)).toBe(
1243+
report,
1244+
);
1245+
});
1246+
1247+
test('should return undefined when deriving a watcher-cycle key from a non-container', () => {
1248+
expect((client as any).getPendingWatcherCycleContainerKey(undefined)).toBeUndefined();
1249+
expect((client as any).getPendingWatcherCycleContainerKey(null)).toBeUndefined();
1250+
});
1251+
1252+
test('should fall back to watcher:name when id is missing', () => {
1253+
expect(
1254+
(client as any).getPendingWatcherCycleContainerKey({
1255+
name: 'test',
1256+
watcher: 'watcher',
1257+
}),
1258+
).toBe('watcher:test');
1259+
});
1260+
1261+
test('should remove the watcher bucket after taking the last pending watcher-cycle report', () => {
1262+
const report = {
1263+
container: {
1264+
id: 'c1',
1265+
name: 'test',
1266+
watcher: 'watcher',
1267+
},
1268+
changed: true,
1269+
};
1270+
(client as any).pendingWatcherCycleReports.set('watcher', new Map([['c1', report]]));
1271+
1272+
expect((client as any).takePendingWatcherCycleReport('watcher', report.container)).toBe(
1273+
report,
1274+
);
1275+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(false);
1276+
});
1277+
1278+
test('should keep the watcher bucket after taking one report when others remain', () => {
1279+
const firstReport = {
1280+
container: {
1281+
id: 'c1',
1282+
name: 'test',
1283+
watcher: 'watcher',
1284+
},
1285+
changed: true,
1286+
};
1287+
const secondReport = {
1288+
container: {
1289+
id: 'c2',
1290+
name: 'test-2',
1291+
watcher: 'watcher',
1292+
},
1293+
changed: true,
1294+
};
1295+
(client as any).pendingWatcherCycleReports.set(
1296+
'watcher',
1297+
new Map([
1298+
['c1', firstReport],
1299+
['c2', secondReport],
1300+
]),
1301+
);
1302+
1303+
expect((client as any).takePendingWatcherCycleReport('watcher', firstReport.container)).toBe(
1304+
firstReport,
1305+
);
1306+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(true);
1307+
expect((client as any).pendingWatcherCycleReports.get('watcher')?.has('c2')).toBe(true);
1308+
});
1309+
1310+
test('should remove the watcher bucket when clearing the last pending watcher-cycle report by id', () => {
1311+
const report = {
1312+
container: {
1313+
id: 'c1',
1314+
name: 'test',
1315+
watcher: 'watcher',
1316+
},
1317+
changed: true,
1318+
};
1319+
(client as any).pendingWatcherCycleReports.set('watcher', new Map([['c1', report]]));
1320+
1321+
(client as any).clearPendingWatcherCycleReportByContainerId('c1');
1322+
1323+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(false);
1324+
});
1325+
1326+
test('should keep the watcher bucket when clearing one watcher-cycle container id and others remain', () => {
1327+
(client as any).pendingWatcherCycleReports.set(
1328+
'watcher',
1329+
new Map([
1330+
[
1331+
'c1',
1332+
{
1333+
container: {
1334+
id: 'c1',
1335+
name: 'test',
1336+
watcher: 'watcher',
1337+
},
1338+
changed: true,
1339+
},
1340+
],
1341+
[
1342+
'c2',
1343+
{
1344+
container: {
1345+
id: 'c2',
1346+
name: 'test-2',
1347+
watcher: 'watcher',
1348+
},
1349+
changed: true,
1350+
},
1351+
],
1352+
]),
1353+
);
1354+
1355+
(client as any).clearPendingWatcherCycleReportByContainerId('c1');
1356+
1357+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(true);
1358+
expect((client as any).pendingWatcherCycleReports.get('watcher')?.has('c2')).toBe(true);
1359+
});
1360+
1361+
test('should remove the watcher bucket after clearing the last watcher-cycle container id', () => {
1362+
(client as any).pendingWatcherCycleReports.set(
1363+
'watcher',
1364+
new Map([
1365+
[
1366+
'c1',
1367+
{
1368+
container: {
1369+
id: 'c1',
1370+
name: 'test',
1371+
watcher: 'watcher',
1372+
},
1373+
changed: true,
1374+
},
1375+
],
1376+
]),
1377+
);
1378+
1379+
(client as any).clearPendingWatcherCycleReportByContainerId('c1');
1380+
1381+
expect((client as any).pendingWatcherCycleReports.has('watcher')).toBe(false);
1382+
});
1383+
11621384
test('should emit update-applied when agent sends dd:update-applied', async () => {
11631385
await client.handleEvent('dd:update-applied', 'local_nginx');
11641386

0 commit comments

Comments
 (0)