Skip to content

Commit 035c754

Browse files
committed
test(audits): reorganize audit check tests by survey object
Separate unit tests from integration tests for better maintainability: - Create individual test files for each audit check in dedicated folders - Update main test files to use mocked audit checks for integration testing - Update README with clear test organization guidelines - add a test for empty audit checks This structure makes it easier to add new audit checks without updating existing integration tests, and provides better isolation for each check.
1 parent a51e56c commit 035c754

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1630
-1419
lines changed

packages/evolution-backend/src/services/audits/__tests__/AuditUtils.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { v4 as uuidV4 } from 'uuid';
88
import slugify from 'slugify';
99

1010
import { mergeWithExisting, convertParamsErrorsToAudits } from '../AuditUtils';
11-
import { Audits } from 'evolution-common/lib/services/audits/types';
11+
import type { Audits } from 'evolution-common/lib/services/audits/types';
1212

1313
const arbitraryUuid = uuidV4();
1414

packages/evolution-backend/src/services/audits/__tests__/Audits.test.ts

Lines changed: 89 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,35 @@
77
import _omit from 'lodash/omit';
88
import auditsDbQueries from '../../../models/audits.db.queries';
99
import { SurveyObjectsAndAuditsFactory } from '../SurveyObjectsAndAuditsFactory';
10-
import { setProjectConfig } from '../../../config/projectConfig';
10+
import { AuditService } from '../AuditService';
11+
import type { AuditForObject } from 'evolution-common/lib/services/audits/types';
12+
import type { Interview } from 'evolution-common/lib/services/baseObjects/interview/Interview';
13+
import type { Household } from 'evolution-common/lib/services/baseObjects/Household';
14+
import type { Home } from 'evolution-common/lib/services/baseObjects/Home';
1115

1216
jest.mock('../../../models/audits.db.queries', () => ({
1317
setAuditsForInterview: jest.fn(),
1418
updateAudit: jest.fn()
1519
}));
20+
jest.mock('../AuditService', () => ({
21+
AuditService: {
22+
auditInterview: jest.fn()
23+
}
24+
}));
25+
1626
const mockSetAudits = auditsDbQueries.setAuditsForInterview as jest.MockedFunction<typeof auditsDbQueries.setAuditsForInterview>;
1727
mockSetAudits.mockImplementation(async (_interviewId, audits) => audits);
1828
const mockUpdateAudit = auditsDbQueries.updateAudit as jest.MockedFunction<typeof auditsDbQueries.updateAudit>;
29+
const mockAuditInterview = jest.mocked(AuditService.auditInterview);
30+
31+
// helper (place near top of file)
32+
const makeServiceResult = (audits: AuditForObject[]) => ({
33+
interview: {} as Interview,
34+
household: {} as Household,
35+
home: {} as Home,
36+
audits,
37+
auditsByObject: { interview: [], household: [], home: [], persons: {}, journeys: {}, visitedPlaces: {}, trips: {}, segments: {} }
38+
});
1939

2040
const interviewId = 3;
2141

@@ -74,113 +94,108 @@ describe('createSurveyObjectsAndSaveAuditsToDb', () => {
7494
uuid: '123e4567-e89b-12d3-a456-426614174000',
7595
participant_id: 1,
7696
is_valid: true,
77-
response: {
78-
fieldA: 'a',
79-
fieldB: 'b',
80-
home: {
81-
_uuid: 'home-uuid-123',
82-
address: '123 Test Street',
83-
city: 'Test City'
84-
},
85-
household: {
86-
_uuid: 'household-uuid-123',
87-
size: 2,
88-
persons: {
89-
'person-uuid-123': {
90-
_uuid: 'person-uuid-123',
91-
age: 30,
92-
_sequence: 1
93-
}
94-
}
95-
}
96-
} as any,
97+
response: {},
9798
validations: {},
9899
is_completed: false,
99-
corrected_response: {
100-
fieldA: 'modifiedA',
101-
fieldB: 'modifiedB',
102-
home: {
103-
_uuid: 'home-uuid-123',
104-
address: '456 Modified Street',
105-
city: 'Modified City'
106-
},
107-
household: {
108-
_uuid: 'household-uuid-123',
109-
size: 3,
110-
persons: {
111-
'person-uuid-123': {
112-
_uuid: 'person-uuid-123',
113-
age: 35,
114-
_sequence: 1
115-
}
116-
}
117-
}
118-
} as any
100+
corrected_response: {}
119101
};
120102

121-
const mockInterviewAudits = jest.fn();
103+
// Mock audits returned by AuditService
104+
const mockAuditsFromService: AuditForObject[] = [
105+
{
106+
objectType: 'interview',
107+
objectUuid: 'interview-uuid-123',
108+
errorCode: 'TEST_ERROR_1',
109+
version: 1,
110+
level: 'error',
111+
message: 'Test error 1',
112+
ignore: false
113+
},
114+
{
115+
objectType: 'household',
116+
objectUuid: 'household-uuid-123',
117+
errorCode: 'TEST_ERROR_2',
118+
version: 1,
119+
level: 'warning',
120+
message: 'Test warning 2',
121+
ignore: false
122+
}
123+
];
122124

123125
beforeEach(() => {
124126
mockSetAudits.mockClear();
125-
mockInterviewAudits.mockClear();
126-
setProjectConfig({ auditInterview: mockInterviewAudits });
127-
});
128-
129-
test('audit function not set in config - uses default implementation', async () => {
130-
// Unset the interview audit function
131-
setProjectConfig({ auditInterview: undefined });
132-
133-
const objectsAndAudits = await SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interviewAttributes);
134-
// Should use default audit implementation, so we expect some audits
135-
expect(objectsAndAudits.audits.length).toBeGreaterThan(0);
136-
expect(mockInterviewAudits).not.toHaveBeenCalled();
127+
mockAuditInterview.mockClear();
128+
// Mock AuditService to return dummy audits
129+
mockAuditInterview.mockResolvedValue(makeServiceResult(mockAuditsFromService));
137130
});
138131

139132
test('corrected_response not set in interview', async () => {
140133
await expect(SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(_omit(interviewAttributes, 'corrected_response')))
141134
.rejects.toThrow('Corrected response is required to create survey objects and audits');
142-
expect(mockInterviewAudits).not.toHaveBeenCalled();
135+
expect(mockAuditInterview).not.toHaveBeenCalled();
143136
});
144137

145-
test('Function returns audits from audit service', async () => {
146-
// The new system uses AuditService which produces real audits
138+
test('Function calls AuditService and returns audits', async () => {
147139
const objectsAndAudits = await SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interviewAttributes);
148140

149-
// Should have some audits from the audit system
150-
expect(objectsAndAudits.audits.length).toBeGreaterThan(0);
151-
152-
// Should have audits for objects with validation errors (home, household, etc.)
153-
// The system only generates audits when there are actual validation errors
154-
expect(objectsAndAudits.audits.length).toBe(3);
141+
// Should call AuditService
142+
expect(mockAuditInterview).toHaveBeenCalledTimes(1);
143+
expect(mockAuditInterview).toHaveBeenCalledWith(interviewAttributes);
155144

156-
// The old mock interview audit function should not be called anymore
157-
expect(mockInterviewAudits).not.toHaveBeenCalled();
145+
// Should return the audits from AuditService
146+
expect(objectsAndAudits.audits).toEqual(mockAuditsFromService);
158147

159148
// Database should be called to save the audits
160149
expect(mockSetAudits).toHaveBeenCalledTimes(1);
161-
expect(mockSetAudits).toHaveBeenCalledWith(interviewId, objectsAndAudits.audits);
150+
expect(mockSetAudits).toHaveBeenCalledWith(interviewId, mockAuditsFromService);
162151
});
163152

164153
test('Function saves audits to database and returns updated audits', async () => {
165154
// Mock the database to add ignore status to all audits when saving
166-
mockSetAudits.mockImplementationOnce(async (_interviewId, audits) =>
167-
audits.map((audit) => ({ ...audit, ignore: true }))
168-
);
155+
const updatedAudits = mockAuditsFromService.map((audit) => ({ ...audit, ignore: true }));
156+
mockSetAudits.mockResolvedValueOnce(updatedAudits);
169157

170158
const objectsAndAudits = await SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interviewAttributes);
171159

172-
// Should have some audits from the audit system
173-
expect(objectsAndAudits.audits.length).toBeGreaterThan(0);
160+
// Should call AuditService
161+
expect(mockAuditInterview).toHaveBeenCalledTimes(1);
174162

175163
// All audits should have the ignore flag set by the database mock
176164
expect(objectsAndAudits.audits.every((audit) => audit.ignore === true)).toBe(true);
177165

178-
// The old mock interview audit function should not be called anymore
179-
expect(mockInterviewAudits).not.toHaveBeenCalled();
166+
// auditsByObject should reflect the updated audits
167+
expect(objectsAndAudits.auditsByObject?.interview ?? []).toEqual(
168+
expect.arrayContaining([expect.objectContaining({ objectType: 'interview', ignore: true })])
169+
);
170+
expect(objectsAndAudits.auditsByObject?.household ?? []).toEqual(
171+
expect.arrayContaining([expect.objectContaining({ objectType: 'household', ignore: true })])
172+
);
180173

181174
// Database should be called to save the audits
182175
expect(mockSetAudits).toHaveBeenCalledTimes(1);
183-
expect(mockSetAudits).toHaveBeenCalledWith(interviewId, expect.any(Array));
176+
expect(mockSetAudits).toHaveBeenCalledWith(interviewId, mockAuditsFromService);
177+
});
178+
179+
test('Function handles empty audits array', async () => {
180+
// Mock AuditService to return no audits
181+
mockAuditInterview.mockResolvedValueOnce(makeServiceResult([]));
182+
183+
const objectsAndAudits = await SurveyObjectsAndAuditsFactory.createSurveyObjectsAndSaveAuditsToDb(interviewAttributes);
184+
185+
// Should call AuditService
186+
expect(mockAuditInterview).toHaveBeenCalledTimes(1);
187+
188+
// Should have no audits
189+
expect(objectsAndAudits.audits).toEqual([]);
190+
191+
// auditsByObject should be properly initialized even with no audits
192+
expect(objectsAndAudits.auditsByObject).toBeDefined();
193+
expect(objectsAndAudits.auditsByObject?.interview).toEqual([]);
194+
expect(objectsAndAudits.auditsByObject?.household).toEqual([]);
195+
196+
// Database should still be called even with empty array
197+
expect(mockSetAudits).toHaveBeenCalledTimes(1);
198+
expect(mockSetAudits).toHaveBeenCalledWith(interviewId, []);
184199
});
185200
});
186201

packages/evolution-backend/src/services/audits/auditChecks/AuditCheckContexts.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,11 @@ export type SegmentAuditCheckContext = {
101101
/**
102102
* Generic audit check function signatures
103103
*/
104-
export type InterviewAuditCheckFunction = (context: InterviewAuditCheckContext) => Partial<AuditForObject> | undefined;
105-
export type HouseholdAuditCheckFunction = (context: HouseholdAuditCheckContext) => Partial<AuditForObject> | undefined;
106-
export type HomeAuditCheckFunction = (context: HomeAuditCheckContext) => Partial<AuditForObject> | undefined;
107-
export type PersonAuditCheckFunction = (context: PersonAuditCheckContext) => Partial<AuditForObject> | undefined;
108-
export type JourneyAuditCheckFunction = (context: JourneyAuditCheckContext) => Partial<AuditForObject> | undefined;
109-
export type VisitedPlaceAuditCheckFunction = (
110-
context: VisitedPlaceAuditCheckContext
111-
) => Partial<AuditForObject> | undefined;
112-
export type TripAuditCheckFunction = (context: TripAuditCheckContext) => Partial<AuditForObject> | undefined;
113-
export type SegmentAuditCheckFunction = (context: SegmentAuditCheckContext) => Partial<AuditForObject> | undefined;
104+
export type InterviewAuditCheckFunction = (context: InterviewAuditCheckContext) => AuditForObject | undefined;
105+
export type HouseholdAuditCheckFunction = (context: HouseholdAuditCheckContext) => AuditForObject | undefined;
106+
export type HomeAuditCheckFunction = (context: HomeAuditCheckContext) => AuditForObject | undefined;
107+
export type PersonAuditCheckFunction = (context: PersonAuditCheckContext) => AuditForObject | undefined;
108+
export type JourneyAuditCheckFunction = (context: JourneyAuditCheckContext) => AuditForObject | undefined;
109+
export type VisitedPlaceAuditCheckFunction = (context: VisitedPlaceAuditCheckContext) => AuditForObject | undefined;
110+
export type TripAuditCheckFunction = (context: TripAuditCheckContext) => AuditForObject | undefined;
111+
export type SegmentAuditCheckFunction = (context: SegmentAuditCheckContext) => AuditForObject | undefined;

packages/evolution-backend/src/services/audits/auditChecks/AuditCheckRunners.ts

Lines changed: 8 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,7 @@ export const runInterviewAuditChecks = (
2020
for (const errorCode in auditChecks) {
2121
const auditResult = auditChecks[errorCode](context);
2222
if (auditResult && context.interview._uuid) {
23-
audits.push({
24-
objectType: 'interview',
25-
objectUuid: context.interview._uuid,
26-
errorCode,
27-
...auditResult,
28-
version: auditResult.version || 1
29-
});
23+
audits.push(auditResult);
3024
}
3125
}
3226

@@ -45,13 +39,7 @@ export const runHouseholdAuditChecks = (
4539
for (const errorCode in auditChecks) {
4640
const auditResult = auditChecks[errorCode](context);
4741
if (auditResult && context.household._uuid) {
48-
audits.push({
49-
objectType: 'household',
50-
objectUuid: context.household._uuid,
51-
errorCode,
52-
...auditResult,
53-
version: auditResult.version || 1
54-
});
42+
audits.push(auditResult);
5543
}
5644
}
5745

@@ -70,13 +58,7 @@ export const runHomeAuditChecks = (
7058
for (const errorCode in auditChecks) {
7159
const auditResult = auditChecks[errorCode](context);
7260
if (auditResult && context.home._uuid) {
73-
audits.push({
74-
objectType: 'home',
75-
objectUuid: context.home._uuid,
76-
errorCode,
77-
...auditResult,
78-
version: auditResult.version || 1
79-
});
61+
audits.push(auditResult);
8062
}
8163
}
8264

@@ -95,13 +77,7 @@ export const runPersonAuditChecks = (
9577
for (const errorCode in auditChecks) {
9678
const auditResult = auditChecks[errorCode](context);
9779
if (auditResult && context.person._uuid) {
98-
audits.push({
99-
objectType: 'person',
100-
objectUuid: context.person._uuid,
101-
errorCode,
102-
...auditResult,
103-
version: auditResult.version || 1
104-
});
80+
audits.push(auditResult);
10581
}
10682
}
10783

@@ -120,13 +96,7 @@ export const runJourneyAuditChecks = (
12096
for (const errorCode in auditChecks) {
12197
const auditResult = auditChecks[errorCode](context);
12298
if (auditResult && context.journey._uuid) {
123-
audits.push({
124-
objectType: 'journey',
125-
objectUuid: context.journey._uuid,
126-
errorCode,
127-
...auditResult,
128-
version: auditResult.version || 1
129-
});
99+
audits.push(auditResult);
130100
}
131101
}
132102

@@ -145,13 +115,7 @@ export const runVisitedPlaceAuditChecks = (
145115
for (const errorCode in auditChecks) {
146116
const auditResult = auditChecks[errorCode](context);
147117
if (auditResult && context.visitedPlace._uuid) {
148-
audits.push({
149-
objectType: 'visitedPlace',
150-
objectUuid: context.visitedPlace._uuid,
151-
errorCode,
152-
...auditResult,
153-
version: auditResult.version || 1
154-
});
118+
audits.push(auditResult);
155119
}
156120
}
157121

@@ -170,13 +134,7 @@ export const runTripAuditChecks = (
170134
for (const errorCode in auditChecks) {
171135
const auditResult = auditChecks[errorCode](context);
172136
if (auditResult && context.trip._uuid) {
173-
audits.push({
174-
objectType: 'trip',
175-
objectUuid: context.trip._uuid,
176-
errorCode,
177-
...auditResult,
178-
version: auditResult.version || 1
179-
});
137+
audits.push(auditResult);
180138
}
181139
}
182140

@@ -195,13 +153,7 @@ export const runSegmentAuditChecks = (
195153
for (const errorCode in auditChecks) {
196154
const auditResult = auditChecks[errorCode](context);
197155
if (auditResult && context.segment._uuid) {
198-
audits.push({
199-
objectType: 'segment',
200-
objectUuid: context.segment._uuid,
201-
errorCode,
202-
...auditResult,
203-
version: auditResult.version || 1
204-
});
156+
audits.push(auditResult);
205157
}
206158
}
207159

packages/evolution-backend/src/services/audits/auditChecks/checks/HomeAuditChecks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import { isFeature, isPoint } from 'geojson-validation';
99

10-
import { AuditForObject } from 'evolution-common/lib/services/audits/types';
11-
import { HomeAuditCheckContext, HomeAuditCheckFunction } from '../AuditCheckContexts';
10+
import type { AuditForObject } from 'evolution-common/lib/services/audits/types';
11+
import type { HomeAuditCheckContext, HomeAuditCheckFunction } from '../AuditCheckContexts';
1212

1313
export const homeAuditChecks: { [errorCode: string]: HomeAuditCheckFunction } = {
1414
/**

packages/evolution-backend/src/services/audits/auditChecks/checks/HouseholdAuditChecks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
* License text available at https://opensource.org/licenses/MIT
66
*/
77

8-
import { AuditForObject } from 'evolution-common/lib/services/audits/types';
9-
import { HouseholdAuditCheckContext, HouseholdAuditCheckFunction } from '../AuditCheckContexts';
8+
import type { AuditForObject } from 'evolution-common/lib/services/audits/types';
9+
import type { HouseholdAuditCheckContext, HouseholdAuditCheckFunction } from '../AuditCheckContexts';
1010

1111
export const householdAuditChecks: { [errorCode: string]: HouseholdAuditCheckFunction } = {
1212
/**

0 commit comments

Comments
 (0)