Skip to content

Commit cd70e05

Browse files
authored
Improve the test results for Integrations internals (#2376)
* Add custom expect handlers for integration results Signed-off-by: Simeon Widdis <[email protected]> * Add context to integrationReader errors Signed-off-by: Simeon Widdis <[email protected]> * Use FileParams type where applicable Signed-off-by: Simeon Widdis <[email protected]> * Assert serialized integrations works as part of usage Signed-off-by: Simeon Widdis <[email protected]> --------- Signed-off-by: Simeon Widdis <[email protected]>
1 parent f80845e commit cd70e05

File tree

6 files changed

+133
-42
lines changed

6 files changed

+133
-42
lines changed

.eslintrc.js

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ module.exports = {
3939
],
4040
},
4141
],
42+
'jest/expect-expect': [
43+
'warn',
44+
{
45+
// Allow using custom expect test helpers as long as the name starts with `expect`.
46+
assertFunctionNames: ["expect*"],
47+
}
48+
]
4249
},
4350
overrides: [
4451
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
/**
7+
* Useful for asserting results are okay, while still having access to error information. A context
8+
* object can be supplied to help provide context if the value of the result doesn't contain enough
9+
* information to know what went wrong.
10+
*/
11+
export const expectOkResult = (result: Result<unknown>, context?: string | object) => {
12+
const labeled = {
13+
...result,
14+
context,
15+
};
16+
expect(labeled).toEqual({
17+
ok: true,
18+
context,
19+
value: expect.anything(),
20+
});
21+
};
22+
23+
/**
24+
* Validate an error result is correctly returned. A context object can be supplied to help provide
25+
* context if the value of the result doesn't contain enough information to know what went wrong.
26+
*/
27+
export const expectErrorResult = (result: Result<unknown>, context?: string | object) => {
28+
const labeled = {
29+
...result,
30+
context,
31+
};
32+
expect(labeled).toEqual({
33+
ok: false,
34+
context,
35+
error: expect.anything(),
36+
});
37+
};

server/adaptors/integrations/__test__/json_repository.test.ts

+17-14
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import path from 'path';
1313
import * as fs from 'fs/promises';
1414
import { JsonCatalogDataAdaptor } from '../repository/json_data_adaptor';
1515
import { deepCheck, foldResults } from '../repository/utils';
16+
import { expectErrorResult, expectOkResult } from './custom_expects';
1617

1718
const fetchSerializedIntegrations = async (): Promise<Result<SerializedIntegration[], Error>> => {
1819
const directory = path.join(__dirname, '../__data__/repository');
@@ -31,14 +32,15 @@ const fetchSerializedIntegrations = async (): Promise<Result<SerializedIntegrati
3132
const serializedIntegrationResults = await Promise.all(
3233
(readers.filter((x) => x !== null) as IntegrationReader[]).map((r) => r.serialize())
3334
);
34-
return foldResults(serializedIntegrationResults);
35+
const folded = foldResults(serializedIntegrationResults);
36+
expectOkResult(folded);
37+
return folded;
3538
};
3639

3740
describe('The Local Serialized Catalog', () => {
3841
it('Should serialize without errors', async () => {
3942
const serialized = await fetchSerializedIntegrations();
40-
expect(serialized.error).not.toBeDefined();
41-
expect(serialized.ok).toBe(true);
43+
expectOkResult(serialized);
4244
});
4345

4446
it('Should pass deep validation for all serialized integrations', async () => {
@@ -49,7 +51,7 @@ describe('The Local Serialized Catalog', () => {
4951

5052
for (const integ of await repository.getIntegrationList()) {
5153
const validationResult = await deepCheck(integ);
52-
await expect(validationResult).toHaveProperty('ok', true);
54+
expectOkResult(validationResult);
5355
}
5456
});
5557

@@ -61,7 +63,7 @@ describe('The Local Serialized Catalog', () => {
6163
const integration = (await repository.getIntegration('nginx')) as IntegrationReader;
6264
const logoStatic = await integration.getStatic('logo.svg');
6365

64-
expect(logoStatic).toHaveProperty('ok', true);
66+
expectOkResult(logoStatic);
6567
expect((logoStatic.value as Buffer).length).toBeGreaterThan(100);
6668
});
6769

@@ -73,7 +75,7 @@ describe('The Local Serialized Catalog', () => {
7375
const integration = (await repository.getIntegration('nginx')) as IntegrationReader;
7476
const logoStatic = await integration.getStatic('dashboard1.png');
7577

76-
expect(logoStatic).toHaveProperty('ok', true);
78+
expectOkResult(logoStatic);
7779
expect((logoStatic.value as Buffer).length).toBeGreaterThan(1000);
7880
});
7981

@@ -95,7 +97,7 @@ describe('The Local Serialized Catalog', () => {
9597

9698
const reader = new IntegrationReader('nginx', new JsonCatalogDataAdaptor([config]));
9799

98-
await expect(reader.getStatic('dark_logo.svg')).resolves.toHaveProperty('ok', true);
100+
expectOkResult(await reader.getStatic('dark_logo.svg'));
99101
});
100102

101103
it('Should correctly re-serialize', async () => {
@@ -108,6 +110,7 @@ describe('The Local Serialized Catalog', () => {
108110
const reader = new IntegrationReader('nginx', new JsonCatalogDataAdaptor([config]));
109111
const reserialized = await reader.serialize();
110112

113+
expectOkResult(reserialized);
111114
expect(reserialized.value).toEqual(config);
112115
});
113116

@@ -130,6 +133,7 @@ describe('The Local Serialized Catalog', () => {
130133
const reader = new IntegrationReader('nginx', new JsonCatalogDataAdaptor([config]));
131134
const reserialized = await reader.serialize();
132135

136+
expectOkResult(reserialized);
133137
expect(reserialized.value).toEqual(config);
134138
});
135139
});
@@ -151,7 +155,7 @@ describe('Integration validation', () => {
151155
new JsonCatalogDataAdaptor(transformedSerialized)
152156
);
153157

154-
await expect(deepCheck(integration)).resolves.toHaveProperty('ok', false);
158+
expectErrorResult(await deepCheck(integration));
155159
});
156160

157161
it('Should correctly fail an integration without assets', async () => {
@@ -170,7 +174,7 @@ describe('Integration validation', () => {
170174
new JsonCatalogDataAdaptor(transformedSerialized)
171175
);
172176

173-
await expect(deepCheck(integration)).resolves.toHaveProperty('ok', false);
177+
expectErrorResult(await deepCheck(integration));
174178
});
175179
});
176180

@@ -197,10 +201,9 @@ describe('JSON Catalog with invalid data', () => {
197201
new JsonCatalogDataAdaptor([baseConfig])
198202
);
199203

200-
await expect(reader.getStatic('logo.svg')).resolves.toHaveProperty('ok', false);
201-
await expect(reader.getStatic('dm_logo.svg')).resolves.toHaveProperty('ok', false);
202-
await expect(reader.getStatic('1.png')).resolves.toHaveProperty('ok', false);
203-
await expect(reader.getStatic('dm_1.png')).resolves.toHaveProperty('ok', false);
204+
for (const img of ['logo.svg', 'dm_logo.svg', '1.png', 'dm_1.png']) {
205+
expectErrorResult(await reader.getStatic(img));
206+
}
204207
});
205208

206209
it('Should report an error on read if a schema has invalid JSON', async () => {
@@ -218,6 +221,6 @@ describe('JSON Catalog with invalid data', () => {
218221
new JsonCatalogDataAdaptor([baseConfig])
219222
);
220223

221-
await expect(reader.getSchemas()).resolves.toHaveProperty('ok', false);
224+
expectErrorResult(await reader.getSchemas());
222225
});
223226
});

server/adaptors/integrations/__test__/local_fs_repository.test.ts

+31-13
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import path from 'path';
1313
import * as fs from 'fs/promises';
1414
import { deepCheck } from '../repository/utils';
1515
import { FileSystemDataAdaptor } from '../repository/fs_data_adaptor';
16+
import { expectOkResult } from './custom_expects';
1617

1718
const repository: TemplateManager = new TemplateManager([
1819
new FileSystemDataAdaptor(path.join(__dirname, '../__data__/repository')),
@@ -31,40 +32,57 @@ describe('The local repository', () => {
3132
}
3233
// Otherwise, all directories must be integrations
3334
const integ = new IntegrationReader(integPath);
34-
await expect(integ.getConfig()).resolves.toMatchObject({ ok: true });
35+
const config = await integ.getConfig();
36+
expectOkResult(config, { integration: integ.name });
3537
})
3638
);
3739
});
3840

3941
it('Should pass deep validation for all local integrations.', async () => {
4042
const integrations: IntegrationReader[] = await repository.getIntegrationList();
4143
await Promise.all(
42-
integrations.map(async (i: IntegrationReader) => {
43-
const result = await deepCheck(i);
44-
if (!result.ok) {
45-
console.error(i.directory, result.error);
46-
}
47-
expect(result.ok).toBe(true);
44+
integrations.map(async (integ: IntegrationReader) => {
45+
const result = await deepCheck(integ);
46+
expectOkResult(result, { integration: integ.name });
4847
})
4948
);
5049
});
5150
});
5251

52+
// Nginx and VPC are specifically used in other tests, so we add dedicated checks for them.
53+
5354
describe('Local Nginx Integration', () => {
5455
it('Should serialize without errors', async () => {
5556
const integration = await repository.getIntegration('nginx');
5657

57-
await expect(integration?.serialize()).resolves.toHaveProperty('ok', true);
58+
expect(integration).not.toBeNull();
59+
expectOkResult(await integration!.serialize());
5860
});
5961

60-
it('Should serialize to include the config', async () => {
62+
it('Should contain its config in its serialized form', async () => {
6163
const integration = await repository.getIntegration('nginx');
6264
const config = await integration!.getConfig();
6365
const serialized = await integration!.serialize();
6466

65-
expect(serialized).toHaveProperty('ok', true);
66-
expect((serialized as { value: object }).value).toMatchObject(
67-
(config as { value: object }).value
68-
);
67+
expectOkResult(serialized);
68+
expect(serialized.value).toMatchObject(config.value!);
69+
});
70+
});
71+
72+
describe('Local VPC Integration', () => {
73+
it('Should serialize without errors', async () => {
74+
const integration = await repository.getIntegration('amazon_vpc_flow');
75+
76+
expect(integration).not.toBeNull();
77+
expectOkResult(await integration!.serialize());
78+
});
79+
80+
it('Should contain its config in its serialized form', async () => {
81+
const integration = await repository.getIntegration('amazon_vpc_flow');
82+
const config = await integration!.getConfig();
83+
const serialized = await integration!.serialize();
84+
85+
expectOkResult(serialized);
86+
expect(serialized.value).toMatchObject(config.value!);
6987
});
7088
});

server/adaptors/integrations/repository/__test__/integration_reader.test.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { IntegrationReader } from '../integration_reader';
88
import { Dirent, Stats } from 'fs';
99
import * as path from 'path';
1010
import { TEST_INTEGRATION_CONFIG } from '../../../../../test/constants';
11+
import { expectOkResult } from '../../__test__/custom_expects';
1112

1213
jest.mock('fs/promises');
1314

@@ -75,15 +76,15 @@ describe('Integration', () => {
7576

7677
const result = await integration.getConfig(TEST_INTEGRATION_CONFIG.version);
7778

78-
expect(result.error?.message).toBe('data/version must be string');
79+
expect(result.error?.message).toContain('data/version must be string');
7980
});
8081

8182
it('should return an error if the config file has syntax errors', async () => {
8283
jest.spyOn(fs, 'readFile').mockResolvedValue('Invalid JSON');
8384

8485
const result = await integration.getConfig(TEST_INTEGRATION_CONFIG.version);
8586

86-
expect(result.error?.message).toBe(
87+
expect(result.error?.message).toContain(
8788
"Unable to parse file 'sample-2.0.0.json' as JSON or NDJson"
8889
);
8990
});
@@ -114,7 +115,7 @@ describe('Integration', () => {
114115

115116
const result = await integration.getAssets(TEST_INTEGRATION_CONFIG.version);
116117

117-
expect(result.ok).toBe(true);
118+
expectOkResult(result);
118119
expect((result as { value: Array<{ data: object[] }> }).value[0].data).toEqual([
119120
{ name: 'asset1' },
120121
{ name: 'asset2' },
@@ -136,7 +137,7 @@ describe('Integration', () => {
136137

137138
const result = await integration.getAssets(TEST_INTEGRATION_CONFIG.version);
138139

139-
expect(result.error?.message).toBe(
140+
expect(result.error?.message).toContain(
140141
"Unable to parse file 'sample-1.0.1.ndjson' as JSON or NDJson"
141142
);
142143
});
@@ -178,7 +179,7 @@ describe('Integration', () => {
178179
);
179180
const result = await integration.getSchemas();
180181

181-
expect(result.error?.message).toBe("data must have required property 'name'");
182+
expect(result.error?.message).toContain("data must have required property 'name'");
182183
});
183184

184185
it('should reject with an error if a mapping file is invalid', async () => {
@@ -188,7 +189,7 @@ describe('Integration', () => {
188189
.mockRejectedValueOnce(new Error('Could not load schema'));
189190

190191
const result = await integration.getSchemas();
191-
expect(result.error?.message).toBe('Could not load schema');
192+
expect(result.error?.message).toContain('Could not load schema');
192193
});
193194
});
194195

@@ -200,8 +201,8 @@ describe('Integration', () => {
200201

201202
const result = await integration.getStatic('logo.png');
202203

203-
expect(result.ok).toBe(true);
204-
expect((result as { value: unknown }).value).toStrictEqual(Buffer.from('logo data', 'ascii'));
204+
expectOkResult(result);
205+
expect(result.value).toStrictEqual(Buffer.from('logo data', 'ascii'));
205206
expect(readFileMock).toBeCalledWith(path.join('sample', 'static', 'logo.png'));
206207
});
207208

@@ -247,7 +248,7 @@ describe('Integration', () => {
247248

248249
const result = await integration.getSampleData();
249250

250-
expect(result.ok).toBe(true);
251+
expectOkResult(result);
251252
expect((result as { value: { sampleData: unknown } }).value.sampleData).toBeNull();
252253
});
253254

@@ -266,7 +267,9 @@ describe('Integration', () => {
266267

267268
const result = await integration.getSampleData();
268269

269-
expect(result.error?.message).toBe("Unable to parse file 'sample.json' as JSON or NDJson");
270+
expect(result.error?.message).toContain(
271+
"Unable to parse file 'sample.json' as JSON or NDJson"
272+
);
270273
});
271274
});
272275
});

0 commit comments

Comments
 (0)