popeye-cli 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -32
- package/cheatsheet.md +407 -0
- package/dist/cli/commands/db.d.ts +10 -0
- package/dist/cli/commands/db.d.ts.map +1 -0
- package/dist/cli/commands/db.js +240 -0
- package/dist/cli/commands/db.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +18 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +255 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/index.d.ts +2 -0
- package/dist/cli/commands/index.d.ts.map +1 -1
- package/dist/cli/commands/index.js +2 -0
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/interactive.d.ts.map +1 -1
- package/dist/cli/interactive.js +96 -0
- package/dist/cli/interactive.js.map +1 -1
- package/dist/generators/admin-wizard.d.ts +25 -0
- package/dist/generators/admin-wizard.d.ts.map +1 -0
- package/dist/generators/admin-wizard.js +123 -0
- package/dist/generators/admin-wizard.js.map +1 -0
- package/dist/generators/all.d.ts.map +1 -1
- package/dist/generators/all.js +10 -3
- package/dist/generators/all.js.map +1 -1
- package/dist/generators/database.d.ts +58 -0
- package/dist/generators/database.d.ts.map +1 -0
- package/dist/generators/database.js +229 -0
- package/dist/generators/database.js.map +1 -0
- package/dist/generators/fullstack.d.ts.map +1 -1
- package/dist/generators/fullstack.js +23 -7
- package/dist/generators/fullstack.js.map +1 -1
- package/dist/generators/index.d.ts +2 -0
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/templates/admin-wizard-python.d.ts +32 -0
- package/dist/generators/templates/admin-wizard-python.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-python.js +425 -0
- package/dist/generators/templates/admin-wizard-python.js.map +1 -0
- package/dist/generators/templates/admin-wizard-react.d.ts +48 -0
- package/dist/generators/templates/admin-wizard-react.d.ts.map +1 -0
- package/dist/generators/templates/admin-wizard-react.js +554 -0
- package/dist/generators/templates/admin-wizard-react.js.map +1 -0
- package/dist/generators/templates/database-docker.d.ts +23 -0
- package/dist/generators/templates/database-docker.d.ts.map +1 -0
- package/dist/generators/templates/database-docker.js +221 -0
- package/dist/generators/templates/database-docker.js.map +1 -0
- package/dist/generators/templates/database-python.d.ts +54 -0
- package/dist/generators/templates/database-python.d.ts.map +1 -0
- package/dist/generators/templates/database-python.js +723 -0
- package/dist/generators/templates/database-python.js.map +1 -0
- package/dist/generators/templates/database-typescript.d.ts +34 -0
- package/dist/generators/templates/database-typescript.d.ts.map +1 -0
- package/dist/generators/templates/database-typescript.js +232 -0
- package/dist/generators/templates/database-typescript.js.map +1 -0
- package/dist/generators/templates/fullstack.d.ts.map +1 -1
- package/dist/generators/templates/fullstack.js +29 -0
- package/dist/generators/templates/fullstack.js.map +1 -1
- package/dist/generators/templates/index.d.ts +5 -0
- package/dist/generators/templates/index.d.ts.map +1 -1
- package/dist/generators/templates/index.js +5 -0
- package/dist/generators/templates/index.js.map +1 -1
- package/dist/state/index.d.ts +10 -0
- package/dist/state/index.d.ts.map +1 -1
- package/dist/state/index.js +22 -0
- package/dist/state/index.js.map +1 -1
- package/dist/types/consensus.d.ts +3 -0
- package/dist/types/consensus.d.ts.map +1 -1
- package/dist/types/consensus.js +1 -0
- package/dist/types/consensus.js.map +1 -1
- package/dist/types/database-runtime.d.ts +86 -0
- package/dist/types/database-runtime.d.ts.map +1 -0
- package/dist/types/database-runtime.js +61 -0
- package/dist/types/database-runtime.js.map +1 -0
- package/dist/types/database.d.ts +85 -0
- package/dist/types/database.d.ts.map +1 -0
- package/dist/types/database.js +71 -0
- package/dist/types/database.js.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/tester.d.ts +138 -0
- package/dist/types/tester.d.ts.map +1 -0
- package/dist/types/tester.js +110 -0
- package/dist/types/tester.js.map +1 -0
- package/dist/types/workflow.d.ts +166 -0
- package/dist/types/workflow.d.ts.map +1 -1
- package/dist/types/workflow.js +14 -0
- package/dist/types/workflow.js.map +1 -1
- package/dist/workflow/db-setup-runner.d.ts +63 -0
- package/dist/workflow/db-setup-runner.d.ts.map +1 -0
- package/dist/workflow/db-setup-runner.js +336 -0
- package/dist/workflow/db-setup-runner.js.map +1 -0
- package/dist/workflow/db-state-machine.d.ts +30 -0
- package/dist/workflow/db-state-machine.d.ts.map +1 -0
- package/dist/workflow/db-state-machine.js +51 -0
- package/dist/workflow/db-state-machine.js.map +1 -0
- package/dist/workflow/execution-mode.js +2 -2
- package/dist/workflow/execution-mode.js.map +1 -1
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.d.ts.map +1 -1
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/index.js.map +1 -1
- package/dist/workflow/task-workflow.d.ts +5 -0
- package/dist/workflow/task-workflow.d.ts.map +1 -1
- package/dist/workflow/task-workflow.js +172 -6
- package/dist/workflow/task-workflow.js.map +1 -1
- package/dist/workflow/tester.d.ts +120 -0
- package/dist/workflow/tester.d.ts.map +1 -0
- package/dist/workflow/tester.js +589 -0
- package/dist/workflow/tester.js.map +1 -0
- package/dist/workflow/workflow-logger.d.ts +1 -1
- package/dist/workflow/workflow-logger.d.ts.map +1 -1
- package/dist/workflow/workflow-logger.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/commands/db.ts +281 -0
- package/src/cli/commands/doctor.ts +273 -0
- package/src/cli/commands/index.ts +2 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/interactive.ts +102 -0
- package/src/generators/admin-wizard.ts +146 -0
- package/src/generators/all.ts +10 -3
- package/src/generators/database.ts +286 -0
- package/src/generators/fullstack.ts +26 -9
- package/src/generators/index.ts +12 -0
- package/src/generators/templates/admin-wizard-python.ts +431 -0
- package/src/generators/templates/admin-wizard-react.ts +560 -0
- package/src/generators/templates/database-docker.ts +227 -0
- package/src/generators/templates/database-python.ts +734 -0
- package/src/generators/templates/database-typescript.ts +238 -0
- package/src/generators/templates/fullstack.ts +29 -0
- package/src/generators/templates/index.ts +5 -0
- package/src/state/index.ts +29 -0
- package/src/types/consensus.ts +3 -0
- package/src/types/database-runtime.ts +69 -0
- package/src/types/database.ts +84 -0
- package/src/types/index.ts +50 -0
- package/src/types/tester.ts +136 -0
- package/src/types/workflow.ts +31 -0
- package/src/workflow/db-setup-runner.ts +391 -0
- package/src/workflow/db-state-machine.ts +58 -0
- package/src/workflow/execution-mode.ts +2 -2
- package/src/workflow/index.ts +3 -0
- package/src/workflow/task-workflow.ts +227 -5
- package/src/workflow/tester.ts +723 -0
- package/src/workflow/workflow-logger.ts +2 -0
- package/tests/generators/admin-wizard-orchestrator.test.ts +64 -0
- package/tests/generators/admin-wizard-templates.test.ts +366 -0
- package/tests/generators/cross-phase-integration.test.ts +383 -0
- package/tests/generators/database.test.ts +456 -0
- package/tests/generators/fe-be-db-integration.test.ts +613 -0
- package/tests/types/database-runtime.test.ts +158 -0
- package/tests/types/database.test.ts +187 -0
- package/tests/types/tester.test.ts +174 -0
- package/tests/workflow/db-setup-runner.test.ts +211 -0
- package/tests/workflow/db-state-machine.test.ts +117 -0
- package/tests/workflow/tester.test.ts +401 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for database setup pipeline runner
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
+
import { promises as fs } from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {
|
|
9
|
+
readEnvFile,
|
|
10
|
+
parseMigrationPrereqs,
|
|
11
|
+
getPackageName,
|
|
12
|
+
resolveBackendDir,
|
|
13
|
+
computePostPipelineStatus,
|
|
14
|
+
} from '../../src/workflow/db-setup-runner.js';
|
|
15
|
+
import type { SetupResult } from '../../src/types/database-runtime.js';
|
|
16
|
+
|
|
17
|
+
// Mock fs for controlled tests
|
|
18
|
+
vi.mock('node:fs', async () => {
|
|
19
|
+
const actual = await vi.importActual('node:fs');
|
|
20
|
+
return {
|
|
21
|
+
...actual,
|
|
22
|
+
promises: {
|
|
23
|
+
...actual.promises,
|
|
24
|
+
readFile: vi.fn(),
|
|
25
|
+
readdir: vi.fn(),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe('readEnvFile', () => {
|
|
35
|
+
it('should parse key=value pairs from .env content', async () => {
|
|
36
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
37
|
+
'DEBUG=true\nDATABASE_URL=postgresql://localhost/db\nPOSTGRES_USER=postgres\n'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const result = await readEnvFile('/some/path/.env');
|
|
41
|
+
expect(result).toEqual({
|
|
42
|
+
DEBUG: 'true',
|
|
43
|
+
DATABASE_URL: 'postgresql://localhost/db',
|
|
44
|
+
POSTGRES_USER: 'postgres',
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should skip comments and empty lines', async () => {
|
|
49
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
50
|
+
'# This is a comment\n\nDATABASE_URL=postgres://host/db\n# Another comment\n'
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const result = await readEnvFile('/some/path/.env');
|
|
54
|
+
expect(result).toEqual({
|
|
55
|
+
DATABASE_URL: 'postgres://host/db',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should strip surrounding quotes from values', async () => {
|
|
60
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
61
|
+
'FOO="bar"\nBAZ=\'qux\'\n'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const result = await readEnvFile('/some/path/.env');
|
|
65
|
+
expect(result).toEqual({
|
|
66
|
+
FOO: 'bar',
|
|
67
|
+
BAZ: 'qux',
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return empty object when file does not exist', async () => {
|
|
72
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
|
|
73
|
+
|
|
74
|
+
const result = await readEnvFile('/nonexistent/.env');
|
|
75
|
+
expect(result).toEqual({});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should handle values containing equals signs', async () => {
|
|
79
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
80
|
+
'DATABASE_URL=postgresql://user:pass@host/db?sslmode=require\n'
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const result = await readEnvFile('/some/.env');
|
|
84
|
+
expect(result.DATABASE_URL).toBe('postgresql://user:pass@host/db?sslmode=require');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('parseMigrationPrereqs', () => {
|
|
89
|
+
it('should extract extension names from migration comments', async () => {
|
|
90
|
+
vi.mocked(fs.readdir).mockResolvedValue(['001_initial.py'] as any);
|
|
91
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
92
|
+
'"""\nInitial migration.\n\n# popeye:requires_extension=vector\n"""\nfrom alembic import op\n'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const result = await parseMigrationPrereqs('/project/apps/backend/migrations');
|
|
96
|
+
expect(result).toEqual(['vector']);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle multiple extensions across multiple files', async () => {
|
|
100
|
+
vi.mocked(fs.readdir).mockResolvedValue(['001_initial.py', '002_search.py'] as any);
|
|
101
|
+
vi.mocked(fs.readFile)
|
|
102
|
+
.mockResolvedValueOnce('# popeye:requires_extension=vector\n')
|
|
103
|
+
.mockResolvedValueOnce('# popeye:requires_extension=pg_trgm\n');
|
|
104
|
+
|
|
105
|
+
const result = await parseMigrationPrereqs('/project/apps/backend/migrations');
|
|
106
|
+
expect(result).toEqual(['vector', 'pg_trgm']);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should not duplicate extension names', async () => {
|
|
110
|
+
vi.mocked(fs.readdir).mockResolvedValue(['001_initial.py', '002_more.py'] as any);
|
|
111
|
+
vi.mocked(fs.readFile)
|
|
112
|
+
.mockResolvedValueOnce('# popeye:requires_extension=vector\n')
|
|
113
|
+
.mockResolvedValueOnce('# popeye:requires_extension=vector\n');
|
|
114
|
+
|
|
115
|
+
const result = await parseMigrationPrereqs('/project/apps/backend/migrations');
|
|
116
|
+
expect(result).toEqual(['vector']);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return empty array when no extensions required', async () => {
|
|
120
|
+
vi.mocked(fs.readdir).mockResolvedValue(['001_initial.py'] as any);
|
|
121
|
+
vi.mocked(fs.readFile).mockResolvedValue('from alembic import op\n');
|
|
122
|
+
|
|
123
|
+
const result = await parseMigrationPrereqs('/project/apps/backend/migrations');
|
|
124
|
+
expect(result).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should return empty array when directory does not exist', async () => {
|
|
128
|
+
vi.mocked(fs.readdir).mockRejectedValue(new Error('ENOENT'));
|
|
129
|
+
|
|
130
|
+
const result = await parseMigrationPrereqs('/nonexistent/migrations');
|
|
131
|
+
expect(result).toEqual([]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should skip non-.py files', async () => {
|
|
135
|
+
vi.mocked(fs.readdir).mockResolvedValue(['README.md', '001_initial.py'] as any);
|
|
136
|
+
vi.mocked(fs.readFile).mockResolvedValue('# popeye:requires_extension=vector\n');
|
|
137
|
+
|
|
138
|
+
const result = await parseMigrationPrereqs('/project/migrations');
|
|
139
|
+
// readFile called only once (for .py file, not .md)
|
|
140
|
+
expect(fs.readFile).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(result).toEqual(['vector']);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('getPackageName', () => {
|
|
146
|
+
it('should derive snake_case name from project state', async () => {
|
|
147
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
148
|
+
JSON.stringify({ name: 'my-cool-project' })
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const result = await getPackageName('/project');
|
|
152
|
+
expect(result).toBe('my_cool_project');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should strip non-alphanumeric characters', async () => {
|
|
156
|
+
vi.mocked(fs.readFile).mockResolvedValue(
|
|
157
|
+
JSON.stringify({ name: 'My-Project!@#' })
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const result = await getPackageName('/project');
|
|
161
|
+
expect(result).toBe('my_project');
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('should return "backend" as fallback when state is unreadable', async () => {
|
|
165
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
|
|
166
|
+
|
|
167
|
+
const result = await getPackageName('/nonexistent');
|
|
168
|
+
expect(result).toBe('backend');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('resolveBackendDir', () => {
|
|
173
|
+
it('should return apps/backend path', () => {
|
|
174
|
+
expect(resolveBackendDir('/project')).toBe(path.join('/project', 'apps', 'backend'));
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('computePostPipelineStatus', () => {
|
|
179
|
+
it('should transition configured -> applying -> ready on success', () => {
|
|
180
|
+
const result: SetupResult = {
|
|
181
|
+
success: true,
|
|
182
|
+
steps: [],
|
|
183
|
+
totalDurationMs: 100,
|
|
184
|
+
finalStatus: 'ready',
|
|
185
|
+
};
|
|
186
|
+
expect(computePostPipelineStatus('configured', result)).toBe('ready');
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it('should transition configured -> applying -> error on failure', () => {
|
|
190
|
+
const result: SetupResult = {
|
|
191
|
+
success: false,
|
|
192
|
+
steps: [],
|
|
193
|
+
totalDurationMs: 50,
|
|
194
|
+
finalStatus: 'error',
|
|
195
|
+
error: 'Connection refused',
|
|
196
|
+
};
|
|
197
|
+
expect(computePostPipelineStatus('configured', result)).toBe('error');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should throw when starting from unconfigured', () => {
|
|
201
|
+
const result: SetupResult = {
|
|
202
|
+
success: true,
|
|
203
|
+
steps: [],
|
|
204
|
+
totalDurationMs: 0,
|
|
205
|
+
finalStatus: 'ready',
|
|
206
|
+
};
|
|
207
|
+
expect(() => computePostPipelineStatus('unconfigured', result)).toThrow(
|
|
208
|
+
/Invalid DB status transition/
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for database lifecycle state machine
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
canTransition,
|
|
8
|
+
transitionDbStatus,
|
|
9
|
+
getAvailableTransitions,
|
|
10
|
+
} from '../../src/workflow/db-state-machine.js';
|
|
11
|
+
|
|
12
|
+
describe('canTransition', () => {
|
|
13
|
+
it('should allow unconfigured -> configured', () => {
|
|
14
|
+
expect(canTransition('unconfigured', 'configured')).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should allow configured -> applying', () => {
|
|
18
|
+
expect(canTransition('configured', 'applying')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should allow configured -> unconfigured (reset)', () => {
|
|
22
|
+
expect(canTransition('configured', 'unconfigured')).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('should allow applying -> ready', () => {
|
|
26
|
+
expect(canTransition('applying', 'ready')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should allow applying -> error', () => {
|
|
30
|
+
expect(canTransition('applying', 'error')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should allow ready -> configured (reconfigure)', () => {
|
|
34
|
+
expect(canTransition('ready', 'configured')).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should allow ready -> unconfigured (reset)', () => {
|
|
38
|
+
expect(canTransition('ready', 'unconfigured')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should allow error -> configured (retry)', () => {
|
|
42
|
+
expect(canTransition('error', 'configured')).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should allow error -> unconfigured (reset)', () => {
|
|
46
|
+
expect(canTransition('error', 'unconfigured')).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should reject unconfigured -> ready (skip steps)', () => {
|
|
50
|
+
expect(canTransition('unconfigured', 'ready')).toBe(false);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should reject unconfigured -> applying (must configure first)', () => {
|
|
54
|
+
expect(canTransition('unconfigured', 'applying')).toBe(false);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should reject ready -> applying (must reconfigure first)', () => {
|
|
58
|
+
expect(canTransition('ready', 'applying')).toBe(false);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should reject error -> ready (must reconfigure and reapply)', () => {
|
|
62
|
+
expect(canTransition('error', 'ready')).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should reject self-transitions', () => {
|
|
66
|
+
expect(canTransition('unconfigured', 'unconfigured')).toBe(false);
|
|
67
|
+
expect(canTransition('ready', 'ready')).toBe(false);
|
|
68
|
+
expect(canTransition('error', 'error')).toBe(false);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('transitionDbStatus', () => {
|
|
73
|
+
it('should return the target status on valid transition', () => {
|
|
74
|
+
expect(transitionDbStatus('unconfigured', 'configured')).toBe('configured');
|
|
75
|
+
expect(transitionDbStatus('configured', 'applying')).toBe('applying');
|
|
76
|
+
expect(transitionDbStatus('applying', 'ready')).toBe('ready');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should throw on invalid transition', () => {
|
|
80
|
+
expect(() => transitionDbStatus('unconfigured', 'ready')).toThrow(
|
|
81
|
+
/Invalid DB status transition/
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('should include available transitions in error message', () => {
|
|
86
|
+
expect(() => transitionDbStatus('unconfigured', 'ready')).toThrow(
|
|
87
|
+
/configured/
|
|
88
|
+
);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('getAvailableTransitions', () => {
|
|
93
|
+
it('should return [configured] for unconfigured', () => {
|
|
94
|
+
const transitions = getAvailableTransitions('unconfigured');
|
|
95
|
+
expect(transitions).toEqual(['configured']);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should return [applying, unconfigured] for configured', () => {
|
|
99
|
+
const transitions = getAvailableTransitions('configured');
|
|
100
|
+
expect(transitions).toEqual(['applying', 'unconfigured']);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should return [ready, error] for applying', () => {
|
|
104
|
+
const transitions = getAvailableTransitions('applying');
|
|
105
|
+
expect(transitions).toEqual(['ready', 'error']);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return [configured, unconfigured] for ready', () => {
|
|
109
|
+
const transitions = getAvailableTransitions('ready');
|
|
110
|
+
expect(transitions).toEqual(['configured', 'unconfigured']);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('should return [configured, unconfigured] for error', () => {
|
|
114
|
+
const transitions = getAvailableTransitions('error');
|
|
115
|
+
expect(transitions).toEqual(['configured', 'unconfigured']);
|
|
116
|
+
});
|
|
117
|
+
});
|