Skip to content

Commit 8c97b13

Browse files
committed
feat: Added integration tests for import and apply + plan, and updated the original destroy tests
1 parent 706b9d7 commit 8c97b13

14 files changed

Lines changed: 372 additions & 21 deletions

src/parser/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { FileReader } from './reader.js';
1111
import { SourceMapCache } from './source-maps.js';
1212
import { YamlParser } from './yaml/yaml-parser.js';
1313

14-
export const CODIFY_FILE_REGEX = /^(.*\\.)?codify(.json|.yaml)$/;
14+
export const CODIFY_FILE_REGEX = /^(.*)?codify(.json|.yaml)$/;
1515

1616
class Parser {
1717
private readonly languageSpecificParsers= {
@@ -38,7 +38,7 @@ class Parser {
3838
if (!isDirectory) {
3939
const fileName = path.basename(dirOrFile);
4040
if (!CODIFY_FILE_REGEX.test(fileName)) {
41-
throw new Error(`Invalid file path provided ${dirOrFile} ${fileName}. Expected the file to be codify.*.json or .yaml `)
41+
throw new Error(`Invalid file path provided ${dirOrFile} ${fileName}. Expected the file to be *codify.json or *codify.yaml `)
4242
}
4343

4444
return [dirOrFile];
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
import path from 'path';
2+
import { DestroyOrchestrator } from '../../../src/orchestrators/destroy.js';
3+
4+
import { describe, it, vi, afterEach, expect } from 'vitest';
5+
import { MockOs } from '../mocks/system.js';
6+
import { Plan } from '../../../src/entities/plan.js';
7+
import { ResourceOperation } from 'codify-schemas';
8+
import { MockReporter } from '../mocks/reporter.js';
9+
import { ApplyOrchestrator } from '../../../src/orchestrators/apply';
10+
11+
vi.mock('../../../src/plugins/plugin.js', async () => {
12+
const { MockPlugin } = await import('../mocks/plugin.js');
13+
return { Plugin: MockPlugin };
14+
})
15+
16+
// The apply orchestrator directly calls plan so this will test both
17+
describe('Apply and plan orchestrator tests', () => {
18+
it('Can apply a resource (create)', async () => {
19+
const reporter = new MockReporter({
20+
validatePlan(plan: Plan) {
21+
// Xcode-tools will always show up in the plan
22+
expect(plan.getResourcePlan('xcode-tools')).toMatchObject({
23+
operation: ResourceOperation.NOOP
24+
})
25+
expect(plan.getResourcePlan('mock')).toMatchObject({
26+
operation: ResourceOperation.CREATE,
27+
});
28+
},
29+
promptApplyConfirmation(): boolean {
30+
return true;
31+
}
32+
});
33+
34+
const applyConfirmationSpy = vi.spyOn(reporter, 'promptConfirmation');
35+
const applyCompleteSpy = vi.spyOn(reporter, 'displayApplyComplete');
36+
37+
console.log(MockOs.get('xcode-tools'))
38+
expect(MockOs.get('mock')).to.be.undefined;
39+
40+
await ApplyOrchestrator.run({
41+
path: path.join(__dirname, 'create.codify.json')
42+
}, reporter)
43+
44+
expect(applyConfirmationSpy).toHaveBeenCalledOnce();
45+
expect(applyCompleteSpy).toHaveBeenCalledOnce();
46+
47+
// This is two because the system by default has xcode-tools installed
48+
// Codify is designed to always install xcode-tools regardless since a lot of the commands depends on it.
49+
expect(MockOs.getAll().size).to.eq(2);
50+
51+
// These values are form the create.codify.json file. Check that they were applied to the system
52+
expect(MockOs.get('mock')).toMatchObject({
53+
propA: 'abc',
54+
propB: 123,
55+
directory: '/home'
56+
})
57+
});
58+
59+
it('Installs xcode-tools if it doesnt exist', async () => {
60+
const reporter = new MockReporter({
61+
validatePlan(plan: Plan) {
62+
expect(plan.getResourcePlan('xcode-tools')).toMatchObject({
63+
operation: ResourceOperation.CREATE,
64+
});
65+
},
66+
promptApplyConfirmation(): boolean {
67+
return true;
68+
}
69+
});
70+
71+
const applyConfirmationSpy = vi.spyOn(reporter, 'promptConfirmation');
72+
const applyCompleteSpy = vi.spyOn(reporter, 'displayApplyComplete');
73+
74+
MockOs.destroy('xcode-tools');
75+
expect(MockOs.get('xcode-tools')).to.be.undefined;
76+
77+
await ApplyOrchestrator.run({
78+
path: path.join(__dirname, 'xcode-tools.codify.json')
79+
}, reporter)
80+
81+
expect(applyConfirmationSpy).toHaveBeenCalledOnce();
82+
expect(applyCompleteSpy).toHaveBeenCalledOnce();
83+
84+
// This is two because the system by default has xcode-tools installed
85+
// Codify is designed to always install xcode-tools regardless since a lot of the commands depends on it.
86+
expect(MockOs.getAll().size).to.eq(1);
87+
88+
// These values are form the codify.json file. Check that they were applied to the system
89+
expect(MockOs.get('xcode-tools')).toMatchObject({})
90+
});
91+
92+
it('Can apply a resource (recreate)', async () => {
93+
const reporter = new MockReporter({
94+
validatePlan(plan: Plan) {
95+
// As always these values are from recreate.codify.json
96+
expect(plan.getResourcePlan('mock')).toMatchObject({
97+
operation: ResourceOperation.RECREATE,
98+
parameters: expect.arrayContaining([
99+
expect.objectContaining({
100+
name: 'propA',
101+
previousValue: 'current',
102+
newValue: 'newPropA',
103+
operation: 'modify'
104+
}),
105+
expect.objectContaining({
106+
name: 'propB',
107+
previousValue: 1,
108+
newValue: 0,
109+
operation: 'modify'
110+
}),
111+
// Special array filtering logic is happening here. Even though we requested ['a', 'b'] and the system has
112+
// ['a', 'b', 'c'], we won't try to delete the additional elements.
113+
expect.objectContaining({
114+
name: 'array',
115+
previousValue: expect.arrayContaining(['a', 'b']),
116+
newValue: expect.arrayContaining(['a', 'b']),
117+
operation: 'noop'
118+
}),
119+
expect.objectContaining({
120+
name: 'directory',
121+
previousValue: '~/home',
122+
newValue: '/home',
123+
operation: 'modify'
124+
})
125+
])
126+
});
127+
}
128+
});
129+
130+
MockOs.create('mock', {
131+
propA: 'current',
132+
propB: 1,
133+
array: ['a', 'b', 'c'],
134+
directory: '~/home'
135+
})
136+
137+
await ApplyOrchestrator.run({
138+
path: path.join(__dirname, 'recreate.codify.json')
139+
}, reporter)
140+
141+
expect(MockOs.get('mock')).to.toMatchObject({
142+
propA: 'newPropA',
143+
propB: 0,
144+
array: ['a', 'b'],
145+
directory: '/home'
146+
})
147+
});
148+
149+
// Very similar to the re-create test except that this time only the array is changed (which is modifiable)
150+
it('Can apply a resource (modify)', async () => {
151+
const reporter = new MockReporter({
152+
validatePlan(plan: Plan) {
153+
// As always these values are from modfiy.codify.json
154+
expect(plan.getResourcePlan('mock')).toMatchObject({
155+
operation: ResourceOperation.MODIFY,
156+
parameters: expect.arrayContaining([
157+
expect.objectContaining({
158+
name: 'propA',
159+
previousValue: 'current',
160+
newValue: 'current',
161+
operation: 'noop'
162+
}),
163+
expect.objectContaining({
164+
name: 'propB',
165+
previousValue: 1,
166+
newValue: 1,
167+
operation: 'noop'
168+
}),
169+
// Special array filtering logic is happening here. Even though we requested ['a', 'b'] and the system has
170+
// ['a', 'b', 'c'], we won't try to delete the additional elements.
171+
expect.objectContaining({
172+
name: 'array',
173+
previousValue: expect.arrayContaining(['a', 'b', 'c']),
174+
newValue: expect.arrayContaining(['a', 'b', 'c', 'd']),
175+
operation: 'modify'
176+
}),
177+
expect.objectContaining({
178+
name: 'directory',
179+
previousValue: '/home',
180+
newValue: '/home',
181+
operation: 'noop'
182+
})
183+
])
184+
});
185+
}
186+
});
187+
188+
MockOs.create('mock', {
189+
propA: 'current',
190+
propB: 1,
191+
array: ['a', 'b', 'c'],
192+
directory: '/home'
193+
})
194+
195+
await ApplyOrchestrator.run({
196+
path: path.join(__dirname, 'modify.codify.json')
197+
}, reporter)
198+
199+
expect(MockOs.get('mock')).to.toMatchObject({
200+
propA: 'current',
201+
propB: 1,
202+
array: ['a', 'b', 'c', 'd'],
203+
directory: '/home'
204+
})
205+
});
206+
207+
afterEach(() => {
208+
vi.resetAllMocks();
209+
MockOs.reset();
210+
})
211+
212+
})
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{ "type": "project", "plugins": { "default": "./test/orchestrator/mocks/plugin.ts" } },
3+
{ "type": "mock", "propA": "abc", "propB": 123, "directory": "/home" }
4+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{ "type": "project", "plugins": { "default": "./test/orchestrator/mocks/plugin.ts" } },
3+
{ "type": "mock", "propA": "current", "propB": 1, "directory": "/home", "array": ["a", "b", "c", "d"] }
4+
]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[
2+
{ "type": "project", "plugins": { "default": "./test/orchestrator/mocks/plugin.ts" } },
3+
{ "type": "mock", "propA": "newPropA", "propB": 0, "directory": "/home", "array": ["a", "b"] }
4+
]
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
{ "type": "project", "plugins": { "default": "./test/orchestrator/mocks/plugin.ts" } }
3+
]

test/orchestrator/destroy/destroy.test.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ vi.mock('../../../src/plugins/plugin.js', async () => {
1414

1515
describe('Destroy orchestrator tests', () => {
1616
it('Can destroy a resource (simple, no required attributes, from Codify.json)', async () => {
17-
console.log('start')
1817
const reporter = new MockReporter({
1918
validatePlan(plan: Plan) {
2019
expect(plan.getResourcePlan('mock.0')).toMatchObject({
@@ -26,7 +25,6 @@ describe('Destroy orchestrator tests', () => {
2625
}
2726
});
2827

29-
console.log('middle')
3028
MockOs.create('mock', {
3129
propA: 'current',
3230
propB: 1,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
{ "type": "project", "plugins": { "default": "./test/orchestrator/mocks/plugin.ts" } },
3+
{ "type": "mock", "propA": "abc", "propB": 123, "directory": "~/home" },
4+
{ "type": "mock", "propA": "abc", "propB": 12, "directory": "~/home" }
5+
]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import path from 'path';
2+
3+
import { describe, it, vi, afterEach, expect } from 'vitest';
4+
import { MockOs } from '../mocks/system.js';
5+
import { MockReporter } from '../mocks/reporter.js';
6+
import { ImportOrchestrator } from '../../../src/orchestrators/import.js';
7+
import { MockResource, MockResourceConfig } from '../mocks/resource.js';
8+
import { ResourceSettings } from 'codify-plugin-lib';
9+
10+
vi.mock('../mocks/get-mock-resources.js', async () => {
11+
return {
12+
getMockResources: () => ([
13+
new class extends MockResource {
14+
getSettings(): ResourceSettings<MockResourceConfig> {
15+
const orgSettings = super.getSettings();
16+
return {
17+
...orgSettings,
18+
import: {
19+
requiredParameters: ['propA', 'propB'],
20+
refreshKeys: ['propA', 'propB', 'directory'],
21+
}
22+
}
23+
}
24+
25+
async refresh(parameters: Partial<MockResourceConfig>): Promise<Array<Partial<MockResourceConfig>> | Partial<MockResourceConfig> | null> {
26+
expect(parameters).toMatchObject({
27+
propA: expect.any(String),
28+
propB: expect.any(String),
29+
directory: null
30+
})
31+
32+
return super.refresh(parameters);
33+
}
34+
}
35+
])
36+
}
37+
})
38+
39+
vi.mock('../../../src/plugins/plugin.js', async () => {
40+
const { MockPlugin } = await import('../mocks/plugin.js');
41+
return { Plugin: MockPlugin };
42+
})
43+
44+
describe('Import orchestrator tests', () => {
45+
it('Can import a resource', async () => {
46+
const reporter = new MockReporter({
47+
askRequiredPropertiesForImport: (requiredParameters) => {
48+
expect(requiredParameters.get('mock')?.length).to.eq(2);
49+
expect(requiredParameters.get('mock')).toEqual(expect.arrayContaining([
50+
expect.objectContaining({
51+
propertyName: 'propA',
52+
propertyType: 'string',
53+
plugin: 'default'
54+
}),
55+
expect.objectContaining({
56+
propertyName: 'propB',
57+
propertyType: 'number',
58+
plugin: 'default'
59+
})
60+
]))
61+
62+
return new Map([
63+
['mock', { propA: 'randomPropA', propB: 'randomPropB' }], // User supplied values
64+
]);
65+
},
66+
displayImportResult: (importResult) => {
67+
expect(importResult.errors.length).to.eq(0);
68+
expect(importResult.result.length).to.eq(1);
69+
expect(importResult.result[0].type).to.eq('mock');
70+
expect(importResult.result[0].parameters).toMatchObject({ // Make sure the system values are returned here
71+
propA: 'currentA',
72+
propB: 'currentB',
73+
directory: '~/home',
74+
})
75+
}
76+
});
77+
78+
const displayImportResultSpy = vi.spyOn(reporter, 'displayImportResult');
79+
80+
MockOs.create('mock', {
81+
propA: 'currentA',
82+
propB: 'currentB',
83+
directory: '~/home'
84+
})
85+
86+
await ImportOrchestrator.run(
87+
{
88+
typeIds: ['mock'],
89+
path: path.join(__dirname, 'codify.json')
90+
},
91+
reporter,
92+
);
93+
94+
expect(displayImportResultSpy).to.be.toHaveBeenCalledOnce();
95+
});
96+
97+
afterEach(() => {
98+
vi.resetAllMocks();
99+
MockOs.reset();
100+
})
101+
102+
})
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { type Resource } from 'codify-plugin-lib';
2+
3+
import { MockResource, MockXcodeToolsResource } from './resource';
4+
5+
export function getMockResources(): Array<Resource<any>> {
6+
return [new MockResource(), new MockXcodeToolsResource()];
7+
}

0 commit comments

Comments
 (0)