@memoarchitect/tools 0.4.4 → 0.4.6
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 +17 -9
- package/package.json +25 -24
- package/packages/tools/lib/bin/memo.js +0 -0
- package/packages/tools/lib/commands/dev.d.ts.map +1 -1
- package/packages/tools/lib/commands/dev.js +6 -1
- package/packages/tools/lib/commands/dev.js.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.d.ts +1 -1
- package/packages/tools/lib/dhf/template-resolver.d.ts.map +1 -1
- package/packages/tools/lib/dhf/template-resolver.js +20 -16
- package/packages/tools/lib/dhf/template-resolver.js.map +1 -1
- package/packages/tools/lib/language/generated/ast.d.ts +1380 -115
- package/packages/tools/lib/language/generated/ast.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/ast.js +1468 -843
- package/packages/tools/lib/language/generated/ast.js.map +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts +1 -1
- package/packages/tools/lib/language/generated/grammar.d.ts.map +1 -1
- package/packages/tools/lib/language/generated/grammar.js +97 -329
- package/packages/tools/lib/language/generated/grammar.js.map +1 -1
- package/packages/tools/lib/language/generated/module.d.ts +1 -1
- package/packages/tools/lib/language/generated/module.js +1 -1
- package/packages/tools/lib/model/ontology-loader.js +1 -1
- package/packages/tools/lib/model/ontology-loader.js.map +1 -1
- package/packages/tools/lib/model/paths.d.ts +6 -4
- package/packages/tools/lib/model/paths.d.ts.map +1 -1
- package/packages/tools/lib/model/paths.js +20 -9
- package/packages/tools/lib/model/paths.js.map +1 -1
- package/packages/tools/lib/__tests__/archetype-parsing.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/archetype-parsing.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/archetype-parsing.test.js +0 -42
- package/packages/tools/lib/__tests__/archetype-parsing.test.js.map +0 -1
- package/packages/tools/lib/__tests__/architecture-discovery.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/architecture-discovery.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/architecture-discovery.test.js +0 -82
- package/packages/tools/lib/__tests__/architecture-discovery.test.js.map +0 -1
- package/packages/tools/lib/__tests__/behavior.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/behavior.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/behavior.test.js +0 -766
- package/packages/tools/lib/__tests__/behavior.test.js.map +0 -1
- package/packages/tools/lib/__tests__/builder.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/builder.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/builder.test.js +0 -728
- package/packages/tools/lib/__tests__/builder.test.js.map +0 -1
- package/packages/tools/lib/__tests__/config-loader.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/config-loader.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/config-loader.test.js +0 -196
- package/packages/tools/lib/__tests__/config-loader.test.js.map +0 -1
- package/packages/tools/lib/__tests__/conformance.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/conformance.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/conformance.test.js +0 -467
- package/packages/tools/lib/__tests__/conformance.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-eval.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-eval.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-eval.test.js +0 -195
- package/packages/tools/lib/__tests__/constraint-eval.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-grammar.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-grammar.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-grammar.test.js +0 -163
- package/packages/tools/lib/__tests__/constraint-grammar.test.js.map +0 -1
- package/packages/tools/lib/__tests__/constraint-loader.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/constraint-loader.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/constraint-loader.test.js +0 -150
- package/packages/tools/lib/__tests__/constraint-loader.test.js.map +0 -1
- package/packages/tools/lib/__tests__/csv-io.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/csv-io.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/csv-io.test.js +0 -317
- package/packages/tools/lib/__tests__/csv-io.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf-doc-store.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf-doc-store.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf-doc-store.test.js +0 -94
- package/packages/tools/lib/__tests__/dhf-doc-store.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.js +0 -39
- package/packages/tools/lib/__tests__/dhf-template-resolver.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dhf.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dhf.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dhf.test.js +0 -393
- package/packages/tools/lib/__tests__/dhf.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dimension-filter.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dimension-filter.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dimension-filter.test.js +0 -85
- package/packages/tools/lib/__tests__/dimension-filter.test.js.map +0 -1
- package/packages/tools/lib/__tests__/dsm.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/dsm.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/dsm.test.js +0 -112
- package/packages/tools/lib/__tests__/dsm.test.js.map +0 -1
- package/packages/tools/lib/__tests__/e2e-workflow.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/e2e-workflow.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/e2e-workflow.test.js +0 -707
- package/packages/tools/lib/__tests__/e2e-workflow.test.js.map +0 -1
- package/packages/tools/lib/__tests__/file-watcher.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/file-watcher.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/file-watcher.test.js +0 -31
- package/packages/tools/lib/__tests__/file-watcher.test.js.map +0 -1
- package/packages/tools/lib/__tests__/impact.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/impact.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/impact.test.js +0 -82
- package/packages/tools/lib/__tests__/impact.test.js.map +0 -1
- package/packages/tools/lib/__tests__/importer.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/importer.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/importer.test.js +0 -336
- package/packages/tools/lib/__tests__/importer.test.js.map +0 -1
- package/packages/tools/lib/__tests__/kind-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/kind-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/kind-registry.test.js +0 -132
- package/packages/tools/lib/__tests__/kind-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/llm.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/llm.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/llm.test.js +0 -275
- package/packages/tools/lib/__tests__/llm.test.js.map +0 -1
- package/packages/tools/lib/__tests__/manifest-resolver.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/manifest-resolver.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/manifest-resolver.test.js +0 -97
- package/packages/tools/lib/__tests__/manifest-resolver.test.js.map +0 -1
- package/packages/tools/lib/__tests__/markdown-to-ir.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/markdown-to-ir.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/markdown-to-ir.test.js +0 -74
- package/packages/tools/lib/__tests__/markdown-to-ir.test.js.map +0 -1
- package/packages/tools/lib/__tests__/methodology-scope.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/methodology-scope.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/methodology-scope.test.js +0 -59
- package/packages/tools/lib/__tests__/methodology-scope.test.js.map +0 -1
- package/packages/tools/lib/__tests__/parser.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/parser.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/parser.test.js +0 -938
- package/packages/tools/lib/__tests__/parser.test.js.map +0 -1
- package/packages/tools/lib/__tests__/plugin.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/plugin.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/plugin.test.js +0 -308
- package/packages/tools/lib/__tests__/plugin.test.js.map +0 -1
- package/packages/tools/lib/__tests__/relationship-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/relationship-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/relationship-registry.test.js +0 -96
- package/packages/tools/lib/__tests__/relationship-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/rule-registry.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/rule-registry.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/rule-registry.test.js +0 -53
- package/packages/tools/lib/__tests__/rule-registry.test.js.map +0 -1
- package/packages/tools/lib/__tests__/toolchain.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/toolchain.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/toolchain.test.js +0 -94
- package/packages/tools/lib/__tests__/toolchain.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-deriver-samples.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-deriver-samples.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-deriver-samples.test.js +0 -39
- package/packages/tools/lib/__tests__/view-deriver-samples.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-kinds.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-kinds.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-kinds.test.js +0 -263
- package/packages/tools/lib/__tests__/view-kinds.test.js.map +0 -1
- package/packages/tools/lib/__tests__/view-layout-store.test.d.ts +0 -2
- package/packages/tools/lib/__tests__/view-layout-store.test.d.ts.map +0 -1
- package/packages/tools/lib/__tests__/view-layout-store.test.js +0 -61
- package/packages/tools/lib/__tests__/view-layout-store.test.js.map +0 -1
|
@@ -1,707 +0,0 @@
|
|
|
1
|
-
// ─── E2E CLI Workflow Tests ───────────────────────────────────────────────────
|
|
2
|
-
//
|
|
3
|
-
// Tests the full workflow: init → parse → validate → completeness → export
|
|
4
|
-
// Uses a temp directory so tests are isolated from the real filesystem.
|
|
5
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
7
|
-
import { execSync } from 'node:child_process';
|
|
8
|
-
import { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs';
|
|
9
|
-
import { join, relative } from 'node:path';
|
|
10
|
-
import { tmpdir } from 'node:os';
|
|
11
|
-
import { VENDOR_ONTOLOGY_PACKAGES_DIR } from '@memoarchitect/tools';
|
|
12
|
-
import { parse as parseYaml } from 'yaml';
|
|
13
|
-
const CLI_PATH = join(__dirname, '../../lib/bin/memo.js');
|
|
14
|
-
const REPO_ROOT = join(__dirname, '../../../..');
|
|
15
|
-
function run(cmd, cwd) {
|
|
16
|
-
return execSync(`node ${CLI_PATH} ${cmd}`, {
|
|
17
|
-
cwd,
|
|
18
|
-
encoding: 'utf-8',
|
|
19
|
-
timeout: 30_000,
|
|
20
|
-
env: { ...process.env, NO_COLOR: '1' },
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
function runMayFail(cmd, cwd) {
|
|
24
|
-
try {
|
|
25
|
-
const stdout = execSync(`node ${CLI_PATH} ${cmd}`, {
|
|
26
|
-
cwd,
|
|
27
|
-
encoding: 'utf-8',
|
|
28
|
-
timeout: 30_000,
|
|
29
|
-
env: { ...process.env, NO_COLOR: '1' },
|
|
30
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
-
});
|
|
32
|
-
return { stdout, exitCode: 0 };
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
// execSync throws on non-zero exit; stdout is still available
|
|
36
|
-
return { stdout: (err.stdout || '') + (err.stderr || ''), exitCode: err.status || 1 };
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
describe('E2E: memo init → validate → export', () => {
|
|
40
|
-
let tmpDir;
|
|
41
|
-
beforeAll(() => {
|
|
42
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'memo-e2e-'));
|
|
43
|
-
});
|
|
44
|
-
afterAll(() => {
|
|
45
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
46
|
-
});
|
|
47
|
-
it('memo init creates project structure with memo.package.yaml', () => {
|
|
48
|
-
const output = run('init test-device', tmpDir);
|
|
49
|
-
expect(output).toContain('Creating MEMO project: test-device');
|
|
50
|
-
expect(output).toContain('Project created');
|
|
51
|
-
const projectDir = join(tmpDir, 'test-device');
|
|
52
|
-
expect(existsSync(projectDir)).toBe(true);
|
|
53
|
-
expect(existsSync(join(projectDir, 'memo.package.yaml'))).toBe(true);
|
|
54
|
-
const starterPath = join(projectDir, 'src', 'catalog', 'starter.sysml');
|
|
55
|
-
expect(existsSync(starterPath)).toBe(true);
|
|
56
|
-
expect(readdirSync(join(projectDir, 'src', 'documents'))).toEqual(['.gitkeep']);
|
|
57
|
-
// Check new-format config content
|
|
58
|
-
const config = parseYaml(readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8'));
|
|
59
|
-
expect(config).toMatchObject({
|
|
60
|
-
name: 'test-device',
|
|
61
|
-
type: 'device',
|
|
62
|
-
extends: '@memoarchitect/medical-modeling-profile',
|
|
63
|
-
});
|
|
64
|
-
expect(readFileSync(starterPath, 'utf-8')).toContain('import memo_medical_device_library::*');
|
|
65
|
-
expect(existsSync(join(projectDir, 'memo.lock.yaml'))).toBe(true);
|
|
66
|
-
const lock = readFileSync(join(projectDir, 'memo.lock.yaml'), 'utf-8');
|
|
67
|
-
expect(lock).not.toContain('test-device');
|
|
68
|
-
});
|
|
69
|
-
it('memo init with no name initializes the current directory', () => {
|
|
70
|
-
const projectDir = join(tmpDir, 'inplace-device');
|
|
71
|
-
mkdirSync(projectDir);
|
|
72
|
-
const output = run('init', projectDir);
|
|
73
|
-
expect(output).toContain('Creating MEMO project: inplace-device');
|
|
74
|
-
expect(output).toContain('Project created in current directory');
|
|
75
|
-
expect(existsSync(join(projectDir, 'memo.package.yaml'))).toBe(true);
|
|
76
|
-
expect(existsSync(join(projectDir, 'src', 'catalog', 'starter.sysml'))).toBe(true);
|
|
77
|
-
const config = parseYaml(readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8'));
|
|
78
|
-
expect(config.name).toBe('inplace-device');
|
|
79
|
-
// Second init in the same directory must refuse
|
|
80
|
-
const { exitCode, stdout } = runMayFail('init', projectDir);
|
|
81
|
-
expect(exitCode).not.toBe(0);
|
|
82
|
-
expect(stdout).toContain('already a MEMO project');
|
|
83
|
-
});
|
|
84
|
-
it('memo init refuses to overwrite existing directory', () => {
|
|
85
|
-
const { exitCode } = runMayFail('init test-device', tmpDir);
|
|
86
|
-
expect(exitCode).not.toBe(0);
|
|
87
|
-
});
|
|
88
|
-
it('memo init --ontology selects a different ontology', () => {
|
|
89
|
-
// Run from REPO_ROOT so ontology packages are discoverable
|
|
90
|
-
const output = run(`init ${join(tmpDir, 'test-core-device')} --ontology @memoarchitect/ontology`, REPO_ROOT);
|
|
91
|
-
expect(output).toContain('Creating MEMO project');
|
|
92
|
-
expect(output).toContain('Project created');
|
|
93
|
-
const projectDir = join(tmpDir, 'test-core-device');
|
|
94
|
-
const config = parseYaml(readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8'));
|
|
95
|
-
expect(config.extends).toBe('@memoarchitect/ontology');
|
|
96
|
-
// SysML should import the ontology
|
|
97
|
-
const sysml = readFileSync(join(projectDir, 'src', 'catalog', 'starter.sysml'), 'utf-8');
|
|
98
|
-
expect(sysml).toContain('import memo_medical_device_library::*');
|
|
99
|
-
expect(existsSync(join(projectDir, 'memo.lock.yaml'))).toBe(true);
|
|
100
|
-
expect(readFileSync(join(projectDir, 'memo.lock.yaml'), 'utf-8')).not.toContain('test-core-device');
|
|
101
|
-
});
|
|
102
|
-
it('memo init inside the workspace locks against the resolved ontology', () => {
|
|
103
|
-
const projectDir = join(REPO_ROOT, 'tmp-e2e-lock-device');
|
|
104
|
-
try {
|
|
105
|
-
const output = run(`init ${projectDir}`, REPO_ROOT);
|
|
106
|
-
expect(output).toContain('Created memo.lock.yaml');
|
|
107
|
-
const lock = readFileSync(join(projectDir, 'memo.lock.yaml'), 'utf-8');
|
|
108
|
-
expect(lock).toContain('ontology: "@memoarchitect/medical-modeling-profile"');
|
|
109
|
-
expect(lock).not.toContain('tmp-e2e-lock-device');
|
|
110
|
-
}
|
|
111
|
-
finally {
|
|
112
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
it('memo init --ontology rejects unknown ontology', () => {
|
|
116
|
-
// Run from REPO_ROOT so ontology packages are discoverable (and validation triggers)
|
|
117
|
-
const { exitCode, stdout } = runMayFail(`init ${join(tmpDir, 'test-bad')} --ontology @memoarchitect/nonexistent`, REPO_ROOT);
|
|
118
|
-
expect(exitCode).not.toBe(0);
|
|
119
|
-
expect(stdout).toContain('not found');
|
|
120
|
-
});
|
|
121
|
-
it('memo init --list-ontologies shows available packages', () => {
|
|
122
|
-
// Run from REPO_ROOT so packages are discoverable
|
|
123
|
-
const output = run('init --list-ontologies', REPO_ROOT);
|
|
124
|
-
expect(output).toContain('@memoarchitect/ontology');
|
|
125
|
-
expect(output).toContain('@memoarchitect/medical-modeling-profile');
|
|
126
|
-
expect(output).toContain('(default)');
|
|
127
|
-
});
|
|
128
|
-
it('memo init --archetype samd creates project with archetype pinned', () => {
|
|
129
|
-
const projectDir = join(tmpDir, 'test-samd');
|
|
130
|
-
const output = run(`init ${projectDir} --archetype samd`, REPO_ROOT);
|
|
131
|
-
expect(output).toContain('Creating MEMO project');
|
|
132
|
-
expect(output).toContain('Project created');
|
|
133
|
-
const config = parseYaml(readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8'));
|
|
134
|
-
expect(config.archetype).toBe('samd');
|
|
135
|
-
});
|
|
136
|
-
it('memo init --archetype blank creates project without archetype field', () => {
|
|
137
|
-
const projectDir = join(tmpDir, 'test-blank');
|
|
138
|
-
const output = run(`init ${projectDir} --archetype blank`, REPO_ROOT);
|
|
139
|
-
expect(output).toContain('Project created');
|
|
140
|
-
const config = parseYaml(readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8'));
|
|
141
|
-
expect(config.archetype).toBeUndefined();
|
|
142
|
-
});
|
|
143
|
-
it('memo init --archetype rejects unknown archetype', () => {
|
|
144
|
-
const projectDir = join(tmpDir, 'test-bad-arch');
|
|
145
|
-
const { exitCode, stdout } = runMayFail(`init ${projectDir} --archetype nonexistent`, REPO_ROOT);
|
|
146
|
-
expect(exitCode).not.toBe(0);
|
|
147
|
-
expect(stdout).toContain('Unknown archetype');
|
|
148
|
-
});
|
|
149
|
-
it('memo init --list-ontologies shows archetypes from SysML', () => {
|
|
150
|
-
const output = run('init --list-ontologies', REPO_ROOT);
|
|
151
|
-
expect(output).toContain('device archetypes');
|
|
152
|
-
expect(output).toContain('samd');
|
|
153
|
-
expect(output).toContain('connected');
|
|
154
|
-
expect(output).toContain('monitoring');
|
|
155
|
-
expect(output).toContain('infusion_pump');
|
|
156
|
-
expect(output).toContain('blank');
|
|
157
|
-
});
|
|
158
|
-
it('memo init --from-example gpca-pump copies example project', () => {
|
|
159
|
-
const projectDir = join(tmpDir, 'test-from-example');
|
|
160
|
-
const output = run(`init ${projectDir} --from-example gpca-pump`, REPO_ROOT);
|
|
161
|
-
expect(output).toContain('Creating project from example');
|
|
162
|
-
expect(output).toContain('gpca-pump');
|
|
163
|
-
expect(output).toContain('Project created');
|
|
164
|
-
expect(existsSync(join(projectDir, 'memo.config.yaml'))).toBe(true);
|
|
165
|
-
expect(existsSync(join(projectDir, 'model'))).toBe(true);
|
|
166
|
-
const modelDir = join(projectDir, 'model');
|
|
167
|
-
const modelEntries = readdirSync(modelDir);
|
|
168
|
-
expect(modelEntries).toEqual(expect.arrayContaining(['catalog', 'samples', 'views']));
|
|
169
|
-
expect(modelEntries.some(f => f.endsWith('.sysml'))).toBe(false);
|
|
170
|
-
const catalogFiles = readdirSync(join(modelDir, 'catalog'));
|
|
171
|
-
expect(catalogFiles.some(f => f.endsWith('.sysml'))).toBe(true);
|
|
172
|
-
});
|
|
173
|
-
it('memo init --example gpca matches gpca-pump by prefix', () => {
|
|
174
|
-
const projectDir = join(tmpDir, 'test-example-prefix');
|
|
175
|
-
const output = run(`init ${projectDir} --example gpca`, REPO_ROOT);
|
|
176
|
-
expect(output).toContain('Creating project from example');
|
|
177
|
-
expect(output).toContain('gpca-pump');
|
|
178
|
-
expect(existsSync(join(projectDir, 'memo.config.yaml'))).toBe(true);
|
|
179
|
-
});
|
|
180
|
-
it('memo init --example with no name copies into the current (empty) directory', () => {
|
|
181
|
-
const projectDir = join(tmpDir, 'test-example-inplace');
|
|
182
|
-
mkdirSync(projectDir);
|
|
183
|
-
const output = run('init --example gpca', projectDir);
|
|
184
|
-
expect(output).toContain('Project created in current directory');
|
|
185
|
-
expect(existsSync(join(projectDir, 'memo.config.yaml'))).toBe(true);
|
|
186
|
-
expect(existsSync(join(projectDir, 'model'))).toBe(true);
|
|
187
|
-
// Refuses to copy into a non-empty directory
|
|
188
|
-
const { exitCode, stdout } = runMayFail('init --example gpca', projectDir);
|
|
189
|
-
expect(exitCode).not.toBe(0);
|
|
190
|
-
expect(stdout).toContain('already a MEMO project');
|
|
191
|
-
});
|
|
192
|
-
it('memo init --from-example rejects unknown example', () => {
|
|
193
|
-
const projectDir = join(tmpDir, 'test-bad-example');
|
|
194
|
-
const { exitCode, stdout } = runMayFail(`init ${projectDir} --from-example nonexistent`, REPO_ROOT);
|
|
195
|
-
expect(exitCode).not.toBe(0);
|
|
196
|
-
expect(stdout).toContain('Unknown example');
|
|
197
|
-
});
|
|
198
|
-
it('memo init --list-ontologies shows available examples', () => {
|
|
199
|
-
const output = run('init --list-ontologies', REPO_ROOT);
|
|
200
|
-
expect(output).toContain('Available examples');
|
|
201
|
-
expect(output).toContain('gpca-pump');
|
|
202
|
-
});
|
|
203
|
-
});
|
|
204
|
-
describe('E2E: ontology lock + change detection', () => {
|
|
205
|
-
let projectDir;
|
|
206
|
-
beforeAll(() => {
|
|
207
|
-
// Create a test project inside the monorepo so config resolution finds ontology packages
|
|
208
|
-
projectDir = join(REPO_ROOT, '.test-lock-' + process.pid);
|
|
209
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
210
|
-
mkdirSync(projectDir, { recursive: true });
|
|
211
|
-
mkdirSync(join(projectDir, 'model'), { recursive: true });
|
|
212
|
-
writeFileSync(join(projectDir, 'memo.config.yaml'), `
|
|
213
|
-
projectName: lock-test
|
|
214
|
-
projectType: device
|
|
215
|
-
extends: "@memoarchitect/medical-modeling-profile"
|
|
216
|
-
`);
|
|
217
|
-
writeFileSync(join(projectDir, 'model', 'device.sysml'), `
|
|
218
|
-
package LockTest {
|
|
219
|
-
import memo_medical_device_library::*;
|
|
220
|
-
part sys : System {
|
|
221
|
-
attribute redefines name = "Lock Test";
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
`);
|
|
225
|
-
});
|
|
226
|
-
afterAll(() => {
|
|
227
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
228
|
-
});
|
|
229
|
-
it('memo lock creates memo.lock.yaml', () => {
|
|
230
|
-
const output = run('lock', projectDir);
|
|
231
|
-
expect(output).toContain('Lock file written');
|
|
232
|
-
expect(output).toContain('@memoarchitect/medical-modeling-profile');
|
|
233
|
-
expect(existsSync(join(projectDir, 'memo.lock.yaml'))).toBe(true);
|
|
234
|
-
const lock = readFileSync(join(projectDir, 'memo.lock.yaml'), 'utf-8');
|
|
235
|
-
expect(lock).toContain('ontology: "@memoarchitect/medical-modeling-profile"');
|
|
236
|
-
expect(lock).toContain('version:');
|
|
237
|
-
expect(lock).toContain('lockedAt:');
|
|
238
|
-
expect(lock).toContain('packages:');
|
|
239
|
-
// Should have the ontology packages in the chain
|
|
240
|
-
expect(lock).toContain('@memoarchitect/ontology');
|
|
241
|
-
expect(lock).toContain('@memoarchitect/medical-modeling-profile');
|
|
242
|
-
});
|
|
243
|
-
it('memo validate succeeds with matching lock', () => {
|
|
244
|
-
// Lock file was created in previous test
|
|
245
|
-
const { stdout, exitCode } = runMayFail('validate', projectDir);
|
|
246
|
-
expect(stdout).toContain('locked to');
|
|
247
|
-
expect(stdout).toContain('Model:');
|
|
248
|
-
expect(exitCode).toBe(0);
|
|
249
|
-
});
|
|
250
|
-
it('memo validate fails when ontology ID changes', () => {
|
|
251
|
-
// Tamper the lock file to simulate an ontology change
|
|
252
|
-
const lockPath = join(projectDir, 'memo.lock.yaml');
|
|
253
|
-
const lock = readFileSync(lockPath, 'utf-8');
|
|
254
|
-
writeFileSync(lockPath, lock.replace('@memoarchitect/medical-modeling-profile', '@memoarchitect/some-other-ontology'));
|
|
255
|
-
const { stdout, exitCode } = runMayFail('validate', projectDir);
|
|
256
|
-
expect(exitCode).not.toBe(0);
|
|
257
|
-
expect(stdout).toContain('Ontology mismatch');
|
|
258
|
-
expect(stdout).toContain('@memoarchitect/some-other-ontology');
|
|
259
|
-
// Restore the lock file for subsequent tests
|
|
260
|
-
writeFileSync(lockPath, lock);
|
|
261
|
-
});
|
|
262
|
-
it('memo validate fails when ontology version changes', () => {
|
|
263
|
-
const lockPath = join(projectDir, 'memo.lock.yaml');
|
|
264
|
-
const lock = readFileSync(lockPath, 'utf-8');
|
|
265
|
-
writeFileSync(lockPath, lock.replace(/version: "[^"]+"\nlockedAt/, 'version: "99.0.0"\nlockedAt'));
|
|
266
|
-
const { stdout, exitCode } = runMayFail('validate', projectDir);
|
|
267
|
-
expect(exitCode).not.toBe(0);
|
|
268
|
-
expect(stdout).toContain('version changed');
|
|
269
|
-
// Restore
|
|
270
|
-
writeFileSync(lockPath, lock);
|
|
271
|
-
});
|
|
272
|
-
it('memo lock regenerates after ontology changes', () => {
|
|
273
|
-
// First tamper the lock
|
|
274
|
-
const lockPath = join(projectDir, 'memo.lock.yaml');
|
|
275
|
-
const oldLock = readFileSync(lockPath, 'utf-8');
|
|
276
|
-
writeFileSync(lockPath, oldLock.replace('@memoarchitect/medical-modeling-profile', '@memoarchitect/some-other-ontology'));
|
|
277
|
-
// Verify validate fails
|
|
278
|
-
const { exitCode: fail } = runMayFail('validate', projectDir);
|
|
279
|
-
expect(fail).not.toBe(0);
|
|
280
|
-
// Regenerate lock
|
|
281
|
-
const output = run('lock', projectDir);
|
|
282
|
-
expect(output).toContain('Lock file written');
|
|
283
|
-
// Now validate should pass
|
|
284
|
-
const { exitCode: pass } = runMayFail('validate', projectDir);
|
|
285
|
-
expect(pass).toBe(0);
|
|
286
|
-
});
|
|
287
|
-
});
|
|
288
|
-
describe('E2E: custom model validation', () => {
|
|
289
|
-
let projectDir;
|
|
290
|
-
beforeAll(() => {
|
|
291
|
-
// Create a test project inside the monorepo so config resolution finds @memoarchitect/medical-modeling-profile
|
|
292
|
-
projectDir = join(REPO_ROOT, '.test-custom-device-' + process.pid);
|
|
293
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
294
|
-
mkdirSync(projectDir, { recursive: true });
|
|
295
|
-
mkdirSync(join(projectDir, 'model'), { recursive: true });
|
|
296
|
-
// Write a minimal config that extends @memoarchitect/medical-modeling-profile
|
|
297
|
-
writeFileSync(join(projectDir, 'memo.config.yaml'), `
|
|
298
|
-
projectName: custom-device
|
|
299
|
-
projectType: device
|
|
300
|
-
extends: "@memoarchitect/medical-modeling-profile"
|
|
301
|
-
`);
|
|
302
|
-
// Write a SysML model with elements and a traced relationship
|
|
303
|
-
writeFileSync(join(projectDir, 'model', 'device.sysml'), `
|
|
304
|
-
package CustomDevice {
|
|
305
|
-
import memo_medical_device_library::*;
|
|
306
|
-
|
|
307
|
-
part mySystem : System {
|
|
308
|
-
attribute redefines name = "Custom Device";
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
part need1 : Requirement {
|
|
312
|
-
attribute redefines source = "User";
|
|
313
|
-
attribute redefines reqId = "REQ-001";
|
|
314
|
-
attribute redefines statement = "User need 1";
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
part sysReq1 : Requirement {
|
|
318
|
-
attribute redefines source = "System";
|
|
319
|
-
attribute redefines reqId = "REQ-002";
|
|
320
|
-
attribute redefines statement = "System requirement 1";
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
connection : TraceTo connect source ::> sysReq1 to target ::> need1;
|
|
324
|
-
}
|
|
325
|
-
`);
|
|
326
|
-
});
|
|
327
|
-
afterAll(() => {
|
|
328
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
329
|
-
});
|
|
330
|
-
it('validates a custom model with elements and relationships', () => {
|
|
331
|
-
const { stdout } = runMayFail('validate', projectDir);
|
|
332
|
-
expect(stdout).toContain('Model:');
|
|
333
|
-
expect(stdout).toContain('3 elements');
|
|
334
|
-
expect(stdout).toContain('1 relationships');
|
|
335
|
-
expect(stdout).toContain('Completeness by Layer');
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
describe('E2E: memo install', () => {
|
|
339
|
-
let projectDir;
|
|
340
|
-
let fakeOntologyDir;
|
|
341
|
-
beforeAll(() => {
|
|
342
|
-
// Create a project inside the monorepo for config resolution
|
|
343
|
-
projectDir = join(REPO_ROOT, '.test-install-' + process.pid);
|
|
344
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
345
|
-
// Use memo init to create a properly configured project
|
|
346
|
-
run(`init ${projectDir}`, REPO_ROOT);
|
|
347
|
-
// Create a fake local ontology package to install
|
|
348
|
-
fakeOntologyDir = join(REPO_ROOT, '.test-fake-ontology-' + process.pid);
|
|
349
|
-
rmSync(fakeOntologyDir, { recursive: true, force: true });
|
|
350
|
-
mkdirSync(fakeOntologyDir, { recursive: true });
|
|
351
|
-
writeFileSync(join(fakeOntologyDir, 'memo.package.yaml'), `
|
|
352
|
-
name: "@test/fake-ontology"
|
|
353
|
-
version: "1.0.0"
|
|
354
|
-
type: ontology
|
|
355
|
-
extends: "@memoarchitect/ontology"
|
|
356
|
-
description: "Fake ontology for testing memo install"
|
|
357
|
-
`);
|
|
358
|
-
mkdirSync(join(fakeOntologyDir, 'sysml', 'custom'), { recursive: true });
|
|
359
|
-
writeFileSync(join(fakeOntologyDir, 'sysml', 'custom', 'custom.sysml'), `
|
|
360
|
-
package FakeOntology {
|
|
361
|
-
part def CustomKind { }
|
|
362
|
-
}
|
|
363
|
-
`);
|
|
364
|
-
});
|
|
365
|
-
afterAll(() => {
|
|
366
|
-
rmSync(projectDir, { recursive: true, force: true });
|
|
367
|
-
rmSync(fakeOntologyDir, { recursive: true, force: true });
|
|
368
|
-
});
|
|
369
|
-
it('memo install with no source uses the lock and leaves resolvable content alone', () => {
|
|
370
|
-
const output = run('install', projectDir);
|
|
371
|
-
expect(output).toContain('already resolvable');
|
|
372
|
-
expect(output).toContain('@memoarchitect/ontology');
|
|
373
|
-
});
|
|
374
|
-
it('memo install --mode local symlinks a local package into memo_packages/', () => {
|
|
375
|
-
const output = run(`install ${fakeOntologyDir} --mode local`, projectDir);
|
|
376
|
-
expect(output).toContain('Installing package (local)');
|
|
377
|
-
expect(output).toContain('Installed @test/fake-ontology');
|
|
378
|
-
// Check symlink was created
|
|
379
|
-
const linkPath = join(projectDir, 'memo_packages', 'fake-ontology');
|
|
380
|
-
expect(existsSync(linkPath)).toBe(true);
|
|
381
|
-
// Check memo.package.yaml has the dependency
|
|
382
|
-
const config = readFileSync(join(projectDir, 'memo.package.yaml'), 'utf-8');
|
|
383
|
-
expect(config).toContain('@test/fake-ontology');
|
|
384
|
-
});
|
|
385
|
-
it('memo install --mode local refuses non-existent path', () => {
|
|
386
|
-
const { exitCode, stdout } = runMayFail('install /nonexistent/path --mode local', projectDir);
|
|
387
|
-
expect(exitCode).not.toBe(0);
|
|
388
|
-
expect(stdout).toContain('does not exist');
|
|
389
|
-
});
|
|
390
|
-
it('memo install detects local paths automatically', () => {
|
|
391
|
-
// Create another fake package
|
|
392
|
-
const anotherDir = join(REPO_ROOT, '.test-another-ontology-' + process.pid);
|
|
393
|
-
rmSync(anotherDir, { recursive: true, force: true });
|
|
394
|
-
mkdirSync(anotherDir, { recursive: true });
|
|
395
|
-
writeFileSync(join(anotherDir, 'memo.package.yaml'), `
|
|
396
|
-
name: "@test/another-ontology"
|
|
397
|
-
version: "2.0.0"
|
|
398
|
-
type: ontology
|
|
399
|
-
description: "Another fake ontology"
|
|
400
|
-
`);
|
|
401
|
-
try {
|
|
402
|
-
const output = run(`install ${anotherDir}`, projectDir);
|
|
403
|
-
expect(output).toContain('Installing package (local)');
|
|
404
|
-
expect(output).toContain('Installed @test/another-ontology');
|
|
405
|
-
}
|
|
406
|
-
finally {
|
|
407
|
-
rmSync(anotherDir, { recursive: true, force: true });
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
it('memo install requires a project config', () => {
|
|
411
|
-
const emptyDir = mkdtempSync(join(tmpdir(), 'memo-empty-'));
|
|
412
|
-
try {
|
|
413
|
-
const { exitCode, stdout } = runMayFail('install some-package', emptyDir);
|
|
414
|
-
expect(exitCode).not.toBe(0);
|
|
415
|
-
expect(stdout).toContain('No memo.package.yaml');
|
|
416
|
-
}
|
|
417
|
-
finally {
|
|
418
|
-
rmSync(emptyDir, { recursive: true, force: true });
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
});
|
|
422
|
-
describe('DD-3: kpar round-trip (GPCA pump)', () => {
|
|
423
|
-
const GPCA_DIR = join(REPO_ROOT, 'memo', 'src', 'examples', 'gpca-pump');
|
|
424
|
-
let extractDir;
|
|
425
|
-
function collectSysmlFiles(dir) {
|
|
426
|
-
const files = [];
|
|
427
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
428
|
-
const full = join(dir, entry.name);
|
|
429
|
-
if (entry.isDirectory()) {
|
|
430
|
-
files.push(...collectSysmlFiles(full));
|
|
431
|
-
}
|
|
432
|
-
else if (entry.name.endsWith('.sysml')) {
|
|
433
|
-
files.push(full);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
return files;
|
|
437
|
-
}
|
|
438
|
-
beforeAll(() => {
|
|
439
|
-
extractDir = mkdtempSync(join(tmpdir(), 'memo-kpar-roundtrip-'));
|
|
440
|
-
run('pack', GPCA_DIR);
|
|
441
|
-
});
|
|
442
|
-
afterAll(() => {
|
|
443
|
-
rmSync(extractDir, { recursive: true, force: true });
|
|
444
|
-
rmSync(join(GPCA_DIR, 'gpca-pump.kpar'), { force: true });
|
|
445
|
-
});
|
|
446
|
-
it('produces a .kpar file', () => {
|
|
447
|
-
expect(existsSync(join(GPCA_DIR, 'gpca-pump.kpar'))).toBe(true);
|
|
448
|
-
});
|
|
449
|
-
it('kpar extracts without errors', () => {
|
|
450
|
-
execSync(`gunzip -c "${join(GPCA_DIR, 'gpca-pump.kpar')}" | tar xf -`, { cwd: extractDir });
|
|
451
|
-
expect(existsSync(join(extractDir, 'manifest.json'))).toBe(true);
|
|
452
|
-
});
|
|
453
|
-
it('manifest lists all source SysML files', () => {
|
|
454
|
-
const manifest = JSON.parse(readFileSync(join(extractDir, 'manifest.json'), 'utf-8'));
|
|
455
|
-
// The kpar packs every source dir of the project (model/ + methodology/).
|
|
456
|
-
const sourceFiles = [
|
|
457
|
-
...collectSysmlFiles(join(GPCA_DIR, 'model')),
|
|
458
|
-
...collectSysmlFiles(join(GPCA_DIR, 'methodology')),
|
|
459
|
-
];
|
|
460
|
-
const manifestSysml = manifest.files.filter((f) => f.endsWith('.sysml'));
|
|
461
|
-
expect(manifest.format).toBe('kpar');
|
|
462
|
-
expect(manifestSysml.length).toBe(sourceFiles.length);
|
|
463
|
-
for (const src of sourceFiles) {
|
|
464
|
-
const rel = relative(GPCA_DIR, src);
|
|
465
|
-
expect(manifestSysml).toContain(rel);
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
it('extracted SysML files are byte-identical to source', () => {
|
|
469
|
-
const sourceFiles = collectSysmlFiles(join(GPCA_DIR, 'model'));
|
|
470
|
-
expect(sourceFiles.length).toBeGreaterThanOrEqual(10);
|
|
471
|
-
const diffs = [];
|
|
472
|
-
for (const src of sourceFiles) {
|
|
473
|
-
const rel = relative(GPCA_DIR, src);
|
|
474
|
-
const extracted = join(extractDir, rel);
|
|
475
|
-
if (!existsSync(extracted)) {
|
|
476
|
-
diffs.push(`MISSING: ${rel}`);
|
|
477
|
-
continue;
|
|
478
|
-
}
|
|
479
|
-
const srcContent = readFileSync(src);
|
|
480
|
-
const extContent = readFileSync(extracted);
|
|
481
|
-
if (!srcContent.equals(extContent)) {
|
|
482
|
-
diffs.push(`CHANGED: ${rel}`);
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (diffs.length > 0) {
|
|
486
|
-
throw new Error(`Round-trip diff is not empty:\n${diffs.join('\n')}`);
|
|
487
|
-
}
|
|
488
|
-
expect(diffs).toHaveLength(0);
|
|
489
|
-
});
|
|
490
|
-
it('config file survives round-trip', () => {
|
|
491
|
-
const srcConfig = readFileSync(join(GPCA_DIR, 'memo.config.yaml'), 'utf-8');
|
|
492
|
-
const extConfig = readFileSync(join(extractDir, 'memo.config.yaml'), 'utf-8');
|
|
493
|
-
expect(extConfig).toBe(srcConfig);
|
|
494
|
-
});
|
|
495
|
-
});
|
|
496
|
-
describe('DD-5: sysand publish --dry-run', () => {
|
|
497
|
-
it('memo sysand publish --dry-run succeeds for ontology', () => {
|
|
498
|
-
const pkgDir = join(REPO_ROOT, VENDOR_ONTOLOGY_PACKAGES_DIR, 'ontology');
|
|
499
|
-
const output = run('sysand publish --dry-run --package @memoarchitect/ontology', pkgDir);
|
|
500
|
-
expect(output).toContain('PASS');
|
|
501
|
-
expect(output).toContain('.kpar');
|
|
502
|
-
expect(output).toContain('All packages pass dry-run');
|
|
503
|
-
});
|
|
504
|
-
it('memo sysand publish --dry-run fails gracefully outside a project', () => {
|
|
505
|
-
const tmpDir = mkdtempSync(join(tmpdir(), 'memo-publish-'));
|
|
506
|
-
const { exitCode } = runMayFail('sysand publish --dry-run', tmpDir);
|
|
507
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
508
|
-
expect(exitCode).not.toBe(0);
|
|
509
|
-
});
|
|
510
|
-
});
|
|
511
|
-
describe('E2E: import ea/cameo/sysand/owl', () => {
|
|
512
|
-
let tmpDir;
|
|
513
|
-
beforeAll(() => {
|
|
514
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'memo-import-e2e-'));
|
|
515
|
-
});
|
|
516
|
-
afterAll(() => {
|
|
517
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
518
|
-
});
|
|
519
|
-
it('memo import ea imports from EA JSON export', () => {
|
|
520
|
-
const eaJson = JSON.stringify({
|
|
521
|
-
elements: [
|
|
522
|
-
{ id: 1, name: 'Overheating', type: 'Class', stereotype: 'Hazard', notes: 'Thermal hazard' },
|
|
523
|
-
{ id: 2, name: 'Temp Monitor', type: 'Class', stereotype: 'RiskControl' },
|
|
524
|
-
],
|
|
525
|
-
connectors: [
|
|
526
|
-
{ id: 1, sourceId: 2, targetId: 1, type: 'Dependency', stereotype: 'mitigates' },
|
|
527
|
-
],
|
|
528
|
-
});
|
|
529
|
-
writeFileSync(join(tmpDir, 'ea-export.json'), eaJson);
|
|
530
|
-
const output = run(`import ea ea-export.json --dry-run`, tmpDir);
|
|
531
|
-
expect(output).toContain('Sparx EA');
|
|
532
|
-
expect(output).toContain('2 mapped');
|
|
533
|
-
expect(output).toContain('Overheating : Hazard');
|
|
534
|
-
expect(output).toContain('Temp_Monitor : RiskControl');
|
|
535
|
-
expect(output).toContain('Mitigates');
|
|
536
|
-
});
|
|
537
|
-
it('memo import cameo imports from Cameo JSON export', () => {
|
|
538
|
-
const cameoJson = JSON.stringify({
|
|
539
|
-
elements: [
|
|
540
|
-
{ id: 'e1', name: 'Shock Hazard', type: 'uml:Class', stereotypes: ['Hazard'] },
|
|
541
|
-
{ id: 'e2', name: 'Insulation', type: 'uml:Class', stereotypes: ['RiskControl'] },
|
|
542
|
-
],
|
|
543
|
-
relationships: [
|
|
544
|
-
{ id: 'r1', sourceId: 'e2', targetId: 'e1', type: 'sysml:Satisfy' },
|
|
545
|
-
],
|
|
546
|
-
});
|
|
547
|
-
writeFileSync(join(tmpDir, 'cameo-export.json'), cameoJson);
|
|
548
|
-
const output = run(`import cameo cameo-export.json --dry-run`, tmpDir);
|
|
549
|
-
expect(output).toContain('MagicDraw/Cameo');
|
|
550
|
-
expect(output).toContain('2 mapped');
|
|
551
|
-
expect(output).toContain('Shock_Hazard : Hazard');
|
|
552
|
-
expect(output).toContain('Insulation : RiskControl');
|
|
553
|
-
});
|
|
554
|
-
it('memo import owl imports from OWL/Turtle', () => {
|
|
555
|
-
const turtle = `
|
|
556
|
-
@prefix memo: <https://example.org/memo#> .
|
|
557
|
-
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
558
|
-
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
559
|
-
@prefix dcterms: <http://purl.org/dc/terms/> .
|
|
560
|
-
|
|
561
|
-
<https://example.org/memo> a owl:Ontology ;
|
|
562
|
-
dcterms:title "Test OWL Import" ;
|
|
563
|
-
owl:versionInfo "1.0.0" ;
|
|
564
|
-
.
|
|
565
|
-
|
|
566
|
-
memo:Hazard a owl:Class ;
|
|
567
|
-
rdfs:label "Hazard" ;
|
|
568
|
-
memo:layer "risk" ;
|
|
569
|
-
memo:sysmlConstruct "part def" ;
|
|
570
|
-
.
|
|
571
|
-
|
|
572
|
-
memo:mitigates a owl:ObjectProperty ;
|
|
573
|
-
rdfs:label "mitigates" ;
|
|
574
|
-
.
|
|
575
|
-
`;
|
|
576
|
-
writeFileSync(join(tmpDir, 'test-ontology.ttl'), turtle);
|
|
577
|
-
const output = run(`import owl test-ontology.ttl --dry-run`, tmpDir);
|
|
578
|
-
expect(output).toContain('OWL/JSON-LD');
|
|
579
|
-
expect(output).toContain('Classes: 1');
|
|
580
|
-
expect(output).toContain('Properties: 1');
|
|
581
|
-
expect(output).toContain('part def Hazard');
|
|
582
|
-
expect(output).toContain('connection def Mitigates');
|
|
583
|
-
});
|
|
584
|
-
it('memo import owl --package-dir creates ontology package', () => {
|
|
585
|
-
const turtle = `
|
|
586
|
-
@prefix memo: <https://example.org/memo#> .
|
|
587
|
-
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
588
|
-
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
589
|
-
|
|
590
|
-
memo:Hazard a owl:Class ;
|
|
591
|
-
rdfs:label "Hazard" ;
|
|
592
|
-
memo:layer "risk" ;
|
|
593
|
-
.
|
|
594
|
-
|
|
595
|
-
memo:Requirement a owl:Class ;
|
|
596
|
-
rdfs:label "Requirement" ;
|
|
597
|
-
memo:layer "requirements" ;
|
|
598
|
-
.
|
|
599
|
-
`;
|
|
600
|
-
writeFileSync(join(tmpDir, 'pkg-test.ttl'), turtle);
|
|
601
|
-
const pkgDir = join(tmpDir, 'imported-pkg');
|
|
602
|
-
run(`import owl pkg-test.ttl --package-dir ${pkgDir} --package test_pkg`, tmpDir);
|
|
603
|
-
expect(existsSync(join(pkgDir, 'memo.package.yaml'))).toBe(true);
|
|
604
|
-
expect(existsSync(join(pkgDir, '.project.json'))).toBe(true);
|
|
605
|
-
expect(existsSync(join(pkgDir, 'sysml', 'index.sysml'))).toBe(true);
|
|
606
|
-
expect(existsSync(join(pkgDir, 'sysml', 'risk', 'risk.sysml'))).toBe(true);
|
|
607
|
-
expect(existsSync(join(pkgDir, 'sysml', 'requirements', 'requirements.sysml'))).toBe(true);
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
describe('E2E: memo check --sysml-compat', () => {
|
|
611
|
-
let tmpDir;
|
|
612
|
-
beforeAll(() => {
|
|
613
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'memo-check-'));
|
|
614
|
-
run('init test-check --template medical', tmpDir);
|
|
615
|
-
});
|
|
616
|
-
afterAll(() => {
|
|
617
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
618
|
-
});
|
|
619
|
-
it('memo check --sysml-compat produces text report', () => {
|
|
620
|
-
const result = runMayFail('check --sysml-compat test-check', tmpDir);
|
|
621
|
-
expect(result.stdout).toContain('SysML Compatibility Check');
|
|
622
|
-
});
|
|
623
|
-
it('memo check --sysml-compat --format json produces valid JSON', () => {
|
|
624
|
-
const result = runMayFail('check --sysml-compat --format json test-check', tmpDir);
|
|
625
|
-
const report = JSON.parse(result.stdout);
|
|
626
|
-
expect(report.tool).toBe('memo-sysml-compat');
|
|
627
|
-
expect(report.summary).toBeDefined();
|
|
628
|
-
expect(typeof report.summary.compatible).toBe('boolean');
|
|
629
|
-
expect(typeof report.summary.elements).toBe('number');
|
|
630
|
-
expect(Array.isArray(report.findings)).toBe(true);
|
|
631
|
-
});
|
|
632
|
-
it('memo check without --sysml-compat shows usage', () => {
|
|
633
|
-
const result = runMayFail('check test-check', tmpDir);
|
|
634
|
-
expect(result.stdout).toContain('--sysml-compat');
|
|
635
|
-
});
|
|
636
|
-
});
|
|
637
|
-
describe('E2E: memo round-trip', () => {
|
|
638
|
-
let tmpDir;
|
|
639
|
-
beforeAll(() => {
|
|
640
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'memo-rt-'));
|
|
641
|
-
run('init test-rt --template medical', tmpDir);
|
|
642
|
-
});
|
|
643
|
-
afterAll(() => {
|
|
644
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
645
|
-
});
|
|
646
|
-
it('memo round-trip --tool syson produces text report', () => {
|
|
647
|
-
const result = runMayFail('round-trip --tool syson test-rt', tmpDir);
|
|
648
|
-
expect(result.stdout).toContain('Round-Trip Conformance');
|
|
649
|
-
expect(result.stdout).toContain('syson');
|
|
650
|
-
});
|
|
651
|
-
it('memo round-trip --format json produces valid JSON', () => {
|
|
652
|
-
const result = runMayFail('round-trip --tool syson --format json test-rt', tmpDir);
|
|
653
|
-
const report = JSON.parse(result.stdout);
|
|
654
|
-
expect(report.tool).toBe('syson');
|
|
655
|
-
expect(report.summary).toBeDefined();
|
|
656
|
-
expect(typeof report.summary.conformant).toBe('boolean');
|
|
657
|
-
expect(typeof report.summary.elementsLost).toBe('number');
|
|
658
|
-
expect(Array.isArray(report.diffs)).toBe(true);
|
|
659
|
-
});
|
|
660
|
-
it('memo round-trip defaults to syson tool', () => {
|
|
661
|
-
const result = runMayFail('round-trip --format json test-rt', tmpDir);
|
|
662
|
-
const report = JSON.parse(result.stdout);
|
|
663
|
-
expect(report.tool).toBe('syson');
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
describe('I-1: memo ontology add-kind', () => {
|
|
667
|
-
let tmpDir;
|
|
668
|
-
beforeAll(() => {
|
|
669
|
-
tmpDir = mkdtempSync(join(tmpdir(), 'memo-add-kind-'));
|
|
670
|
-
});
|
|
671
|
-
afterAll(() => {
|
|
672
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
673
|
-
});
|
|
674
|
-
it('writes a parser-valid .sysml file for a simple layer', () => {
|
|
675
|
-
run('ontology add-kind MyHazard --layer risk', tmpDir);
|
|
676
|
-
const outPath = join(tmpDir, 'ontology', 'risk', 'MyHazard.sysml');
|
|
677
|
-
expect(existsSync(outPath)).toBe(true);
|
|
678
|
-
const content = readFileSync(outPath, 'utf-8');
|
|
679
|
-
expect(content).toContain('part def MyHazard specializes TraceableElement');
|
|
680
|
-
expect(content).toContain('package risk {');
|
|
681
|
-
expect(content).toContain('private import memo::core::common::*;');
|
|
682
|
-
});
|
|
683
|
-
it('supports nested layer paths', () => {
|
|
684
|
-
run('ontology add-kind SafetyControl --layer architecture/risk', tmpDir);
|
|
685
|
-
const outPath = join(tmpDir, 'ontology', 'architecture', 'risk', 'SafetyControl.sysml');
|
|
686
|
-
expect(existsSync(outPath)).toBe(true);
|
|
687
|
-
const content = readFileSync(outPath, 'utf-8');
|
|
688
|
-
expect(content).toContain('package architecture {');
|
|
689
|
-
expect(content).toContain('package risk {');
|
|
690
|
-
expect(content).toContain('part def SafetyControl specializes TraceableElement');
|
|
691
|
-
});
|
|
692
|
-
it('rejects non-PascalCase kind names', () => {
|
|
693
|
-
const result = runMayFail('ontology add-kind lowercase --layer risk', tmpDir);
|
|
694
|
-
expect(result.exitCode).not.toBe(0);
|
|
695
|
-
});
|
|
696
|
-
it('refuses to overwrite an existing file', () => {
|
|
697
|
-
// MyHazard.sysml already exists from the first test — second call must fail
|
|
698
|
-
const result = runMayFail('ontology add-kind MyHazard --layer risk', tmpDir);
|
|
699
|
-
expect(result.exitCode).not.toBe(0);
|
|
700
|
-
});
|
|
701
|
-
it('does not write any YAML or JSON files', () => {
|
|
702
|
-
const entries = readdirSync(join(tmpDir, 'ontology', 'risk'));
|
|
703
|
-
const nonSysml = entries.filter(f => !f.endsWith('.sysml'));
|
|
704
|
-
expect(nonSysml).toHaveLength(0);
|
|
705
|
-
});
|
|
706
|
-
});
|
|
707
|
-
//# sourceMappingURL=e2e-workflow.test.js.map
|