Skip to content

Commit 93a9103

Browse files
committed
add tests
1 parent a48c7fa commit 93a9103

File tree

7 files changed

+198
-12
lines changed

7 files changed

+198
-12
lines changed

packages/compass-e2e-tests/helpers/selectors.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,8 @@ export const ValidationActionSelector =
11611161
'[data-testid="validation-action-selector"]';
11621162
export const ValidationLevelSelector =
11631163
'[data-testid="validation-level-selector"]';
1164+
export const GenerateValidationRulesButton =
1165+
'[data-testid="generate-rules-button"]';
11641166

11651167
// Find (Documents and Schema tabs)
11661168
export const queryBar = (tabName: string): string => {

packages/compass-e2e-tests/tests/collection-validation-tab.test.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import type { Compass } from '../helpers/compass';
99
import * as Selectors from '../helpers/selectors';
1010
import { createNumbersCollection } from '../helpers/insert-data';
11+
import { expect } from 'chai';
1112

1213
const NO_PREVIEW_DOCUMENTS = 'No Preview Documents';
1314
const PASSING_VALIDATOR = '{ $jsonSchema: {} }';
@@ -52,6 +53,43 @@ describe('Collection validation tab', function () {
5253
await browser.setValidation(validation);
5354
}
5455

56+
context('when the schema validation is empty', function () {
57+
before(async function () {
58+
await browser.setFeature('enableExportSchema', true);
59+
});
60+
61+
it.only('provides users with a button to generate rules', async function () {
62+
await browser.clickVisible(Selectors.GenerateValidationRulesButton);
63+
const editor = browser.$(Selectors.ValidationEditor);
64+
await editor.waitForDisplayed();
65+
66+
// rules are generated
67+
const generatedRules = await browser.getCodemirrorEditorText(
68+
Selectors.ValidationEditor
69+
);
70+
expect(JSON.parse(generatedRules)).to.deep.equal({
71+
$jsonSchema: {
72+
bsonType: 'object',
73+
required: ['_id', 'i', 'j'],
74+
properties: {
75+
_id: {
76+
bsonType: 'objectId',
77+
},
78+
i: {
79+
bsonType: 'int',
80+
},
81+
j: {
82+
bsonType: 'int',
83+
},
84+
},
85+
},
86+
});
87+
88+
// generated rules can be edited and saved
89+
await browser.setValidation('{ $jsonSchema: { "blah": "string" } }');
90+
});
91+
});
92+
5593
context('when the schema validation is set or modified', function () {
5694
it('provides users with a single button to load sample documents', async function () {
5795
await addValidation(PASSING_VALIDATOR);

packages/compass-schema-validation/src/components/validation-states.spec.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ const { renderWithConnections } = createPluginTestHelpers(
3131
describe('ValidationStates [Component]', function () {
3232
let props: any;
3333

34-
const render = (props: any) => {
35-
return renderWithConnections(<ValidationStates {...props} />);
34+
const render = (props: any, options: any = {}) => {
35+
return renderWithConnections(<ValidationStates {...props} />, options);
3636
};
3737

3838
beforeEach(function () {
@@ -255,13 +255,18 @@ describe('ValidationStates [Component]', function () {
255255
props.isZeroState = true;
256256
props.isLoaded = true;
257257
props.serverVersion = '3.2.0';
258-
259-
render(props);
260258
});
261259

262260
it('renders the zero state', function () {
261+
render(props);
263262
expect(screen.getByTestId('empty-content')).to.exist;
264263
});
264+
265+
it('when enableExportSchema is set, shows button for rules generation', function () {
266+
render(props, { preferences: { enableExportSchema: true } });
267+
const btn = screen.getByRole('button', { name: 'Generate rules' });
268+
expect(btn).to.be.visible;
269+
});
265270
});
266271

267272
context('when it is not in the zero state and not loaded', function () {

packages/compass-schema-validation/src/modules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {
3636
INITIAL_STATE as RULES_GENERATION_STATE,
3737
rulesGenerationReducer,
3838
} from './rules-generation';
39+
import type { analyzeSchema } from '@mongodb-js/compass-schema';
3940

4041
/**
4142
* Reset action constant.
@@ -84,6 +85,7 @@ export type SchemaValidationExtraArgs = {
8485
logger: Logger;
8586
track: TrackFunction;
8687
rulesGenerationAbortControllerRef: { current?: AbortController };
88+
analyzeSchema: typeof analyzeSchema;
8789
};
8890

8991
export type SchemaValidationThunkAction<

packages/compass-schema-validation/src/modules/rules-generation.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { SchemaValidationThunkAction } from '.';
22
import { zeroStateChanged } from './zero-state';
33
import { enableEditRules } from './edit-mode';
4-
import { analyzeSchema } from '@mongodb-js/compass-schema';
54
import type { MongoError } from 'mongodb';
65
import type { Action, AnyAction, Reducer } from 'redux';
76
import { validationLevelChanged, validatorChanged } from './validation';
@@ -161,7 +160,13 @@ export const generateValidationRules = (): SchemaValidationThunkAction<
161160
return async (
162161
dispatch,
163162
getState,
164-
{ dataService, logger, preferences, rulesGenerationAbortControllerRef }
163+
{
164+
dataService,
165+
logger,
166+
preferences,
167+
rulesGenerationAbortControllerRef,
168+
analyzeSchema,
169+
}
165170
) => {
166171
dispatch({ type: RulesGenerationActions.generationStarted });
167172

packages/compass-schema-validation/src/stores/store.spec.ts

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import { createNoopTrack } from '@mongodb-js/compass-telemetry/provider';
2020
import type { ConnectionInfoRef } from '@mongodb-js/compass-connections/provider';
2121
import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider';
2222
import Sinon from 'sinon';
23+
import {
24+
generateValidationRules,
25+
stopRulesGeneration,
26+
} from '../modules/rules-generation';
27+
import { waitFor } from '@mongodb-js/testing-library-compass';
2328

2429
const topologyDescription = {
2530
type: 'Unknown',
@@ -39,14 +44,16 @@ const fakeDataService = {
3944
new Promise(() => {
4045
/* never resolves */
4146
}),
47+
isCancelError: () => false,
48+
sample: () => [{ prop1: 'abc' }],
4249
} as any;
4350

4451
const fakeWorkspaces = {
4552
onTabReplace: () => {},
4653
onTabClose: () => {},
4754
} as unknown as WorkspacesService;
4855

49-
const getMockedStore = async () => {
56+
const getMockedStore = async (analyzeSchema: any) => {
5057
const globalAppRegistry = new AppRegistry();
5158
const connectionInfoRef = {
5259
current: {},
@@ -63,11 +70,16 @@ const getMockedStore = async () => {
6370
track: createNoopTrack(),
6471
connectionInfoRef,
6572
},
66-
createActivateHelpers()
73+
createActivateHelpers(),
74+
analyzeSchema
6775
);
6876
return activateResult;
6977
};
7078

79+
const schemaAccessor = {
80+
getMongoDBJsonSchema: () => ({ required: ['prop1'] }),
81+
};
82+
7183
describe('Schema Validation Store', function () {
7284
let store: Store<RootState, RootAction>;
7385
let deactivate: null | (() => void) = null;
@@ -77,7 +89,8 @@ describe('Schema Validation Store', function () {
7789
sandbox = Sinon.createSandbox();
7890
fakeWorkspaces.onTabClose = sandbox.stub();
7991
fakeWorkspaces.onTabReplace = sandbox.stub();
80-
const activateResult = await getMockedStore();
92+
const fakeAnalyzeSchema = sandbox.fake.resolves(schemaAccessor);
93+
const activateResult = await getMockedStore(fakeAnalyzeSchema);
8194
store = activateResult.store;
8295
deactivate = activateResult.deactivate;
8396
});
@@ -278,5 +291,121 @@ describe('Schema Validation Store', function () {
278291
store.dispatch(validationLevelChanged(validationLevel));
279292
});
280293
});
294+
295+
context('when the action is generateValidationRules', function () {
296+
it('executes rules generation', async function () {
297+
store.dispatch(generateValidationRules() as any);
298+
299+
await waitFor(() => {
300+
expect(store.getState().rulesGeneration.isInProgress).to.equal(true);
301+
});
302+
await waitFor(() => {
303+
expect(
304+
JSON.parse(store.getState().validation.validator)
305+
).to.deep.equal({
306+
$jsonSchema: {
307+
required: ['prop1'],
308+
},
309+
});
310+
expect(store.getState().rulesGeneration.isInProgress).to.equal(false);
311+
expect(store.getState().rulesGeneration.error).to.be.undefined;
312+
});
313+
});
314+
315+
it('rules generation can be aborted', async function () {
316+
store.dispatch(generateValidationRules() as any);
317+
318+
await waitFor(() => {
319+
expect(store.getState().rulesGeneration.isInProgress).to.equal(true);
320+
});
321+
322+
store.dispatch(stopRulesGeneration() as any);
323+
await waitFor(() => {
324+
expect(store.getState().validation.validator).to.equal('');
325+
expect(store.getState().rulesGeneration.isInProgress).to.equal(false);
326+
expect(store.getState().rulesGeneration.error).to.be.undefined;
327+
});
328+
});
329+
330+
context('rules generation failure', function () {
331+
it('handles general error', async function () {
332+
const fakeAnalyzeSchema = sandbox.fake.rejects(
333+
new Error('Such a failure')
334+
);
335+
const activateResult = await getMockedStore(fakeAnalyzeSchema);
336+
store = activateResult.store;
337+
deactivate = activateResult.deactivate;
338+
store.dispatch(generateValidationRules() as any);
339+
340+
await waitFor(() => {
341+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
342+
true
343+
);
344+
});
345+
346+
await waitFor(() => {
347+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
348+
false
349+
);
350+
expect(store.getState().rulesGeneration.error).to.deep.equal({
351+
errorMessage: 'Such a failure',
352+
errorType: 'general',
353+
});
354+
});
355+
});
356+
357+
it('handles complexity error', async function () {
358+
const fakeAnalyzeSchema = sandbox.fake.rejects(
359+
new Error('Schema analysis aborted: Fields count above 1000')
360+
);
361+
const activateResult = await getMockedStore(fakeAnalyzeSchema);
362+
store = activateResult.store;
363+
deactivate = activateResult.deactivate;
364+
store.dispatch(generateValidationRules() as any);
365+
366+
await waitFor(() => {
367+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
368+
true
369+
);
370+
});
371+
372+
await waitFor(() => {
373+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
374+
false
375+
);
376+
expect(store.getState().rulesGeneration.error).to.deep.equal({
377+
errorMessage: 'Schema analysis aborted: Fields count above 1000',
378+
errorType: 'highComplexity',
379+
});
380+
});
381+
});
382+
383+
it('handles timeout error', async function () {
384+
const timeoutError: any = new Error('Too long, didnt execute');
385+
timeoutError.code = 50;
386+
const fakeAnalyzeSchema = sandbox.fake.rejects(timeoutError);
387+
const activateResult = await getMockedStore(fakeAnalyzeSchema);
388+
store = activateResult.store;
389+
deactivate = activateResult.deactivate;
390+
store.dispatch(generateValidationRules() as any);
391+
392+
await waitFor(() => {
393+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
394+
true
395+
);
396+
});
397+
398+
await waitFor(() => {
399+
expect(store.getState().rulesGeneration.isInProgress).to.equal(
400+
false
401+
);
402+
expect(store.getState().rulesGeneration.error).to.deep.equal({
403+
errorMessage: 'Too long, didnt execute',
404+
errorType: 'timeout',
405+
});
406+
});
407+
});
408+
});
409+
});
281410
});
282411
});

packages/compass-schema-validation/src/stores/store.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { PreferencesAccess } from 'compass-preferences-model';
1414
import type { Logger } from '@mongodb-js/compass-logging/provider';
1515
import type { TrackFunction } from '@mongodb-js/compass-telemetry';
1616
import { type WorkspacesService } from '@mongodb-js/compass-workspaces/provider';
17+
import { analyzeSchema as compassAnalyzeSchema } from '@mongodb-js/compass-schema';
1718

1819
/**
1920
* The lowest supported version.
@@ -43,7 +44,8 @@ export function configureStore(
4344
| 'logger'
4445
| 'track'
4546
| 'connectionInfoRef'
46-
>
47+
>,
48+
analyzeSchema = compassAnalyzeSchema
4749
) {
4850
const rulesGenerationAbortControllerRef = {
4951
current: undefined,
@@ -58,6 +60,7 @@ export function configureStore(
5860
thunk.withExtraArgument({
5961
...services,
6062
rulesGenerationAbortControllerRef,
63+
analyzeSchema,
6164
})
6265
)
6366
);
@@ -78,7 +81,8 @@ export function onActivated(
7881
workspaces,
7982
track,
8083
}: SchemaValidationServices,
81-
{ on, cleanup, addCleanup }: ActivateHelpers
84+
{ on, cleanup, addCleanup }: ActivateHelpers,
85+
analyzeSchema?: typeof compassAnalyzeSchema
8286
) {
8387
const store = configureStore(
8488
{
@@ -100,7 +104,8 @@ export function onActivated(
100104
workspaces,
101105
logger,
102106
track,
103-
}
107+
},
108+
analyzeSchema
104109
);
105110

106111
// isWritable can change later

0 commit comments

Comments
 (0)