@celilo/cli 0.1.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.
Files changed (267) hide show
  1. package/README.md +1566 -0
  2. package/bin/celilo +16 -0
  3. package/drizzle/0000_complex_puma.sql +179 -0
  4. package/drizzle/0001_dizzy_wolfpack.sql +2 -0
  5. package/drizzle/0002_web_routes.sql +16 -0
  6. package/drizzle/0003_backup_storage.sql +32 -0
  7. package/drizzle/meta/0000_snapshot.json +1151 -0
  8. package/drizzle/meta/0001_snapshot.json +1167 -0
  9. package/drizzle/meta/0002_snapshot.json +1257 -0
  10. package/drizzle/meta/_journal.json +27 -0
  11. package/package.json +64 -0
  12. package/schemas/system_config.json +106 -0
  13. package/src/__integration__/container-services-cli.integration.test.ts +246 -0
  14. package/src/ansible/dependencies.test.ts +309 -0
  15. package/src/ansible/dependencies.ts +896 -0
  16. package/src/ansible/inventory.test.ts +463 -0
  17. package/src/ansible/inventory.ts +445 -0
  18. package/src/ansible/secrets.ts +222 -0
  19. package/src/ansible/validation.test.ts +92 -0
  20. package/src/ansible/validation.ts +272 -0
  21. package/src/api-clients/digitalocean.ts +94 -0
  22. package/src/api-clients/proxmox.ts +655 -0
  23. package/src/capabilities/logging-wrapper.test.ts +217 -0
  24. package/src/capabilities/lookup.test.ts +149 -0
  25. package/src/capabilities/lookup.ts +89 -0
  26. package/src/capabilities/public-web-helpers.test.ts +198 -0
  27. package/src/capabilities/public-web-publish.test.ts +458 -0
  28. package/src/capabilities/registration.test.ts +395 -0
  29. package/src/capabilities/registration.ts +200 -0
  30. package/src/capabilities/route-validation.test.ts +121 -0
  31. package/src/capabilities/route-validation.ts +96 -0
  32. package/src/capabilities/secret-ref.test.ts +313 -0
  33. package/src/capabilities/secret-validation.ts +157 -0
  34. package/src/capabilities/secrets.test.ts +750 -0
  35. package/src/capabilities/secrets.ts +244 -0
  36. package/src/capabilities/validation.test.ts +613 -0
  37. package/src/capabilities/validation.ts +160 -0
  38. package/src/capabilities/well-known.test.ts +238 -0
  39. package/src/capabilities/well-known.ts +222 -0
  40. package/src/cli/cli.test.ts +654 -0
  41. package/src/cli/command-registry.ts +742 -0
  42. package/src/cli/command-tree-parser.test.ts +180 -0
  43. package/src/cli/command-tree-parser.ts +193 -0
  44. package/src/cli/commands/backup-create.ts +137 -0
  45. package/src/cli/commands/backup-delete.ts +74 -0
  46. package/src/cli/commands/backup-import.ts +97 -0
  47. package/src/cli/commands/backup-list.ts +132 -0
  48. package/src/cli/commands/backup-name.ts +73 -0
  49. package/src/cli/commands/backup-prune.ts +98 -0
  50. package/src/cli/commands/backup-restore.ts +122 -0
  51. package/src/cli/commands/capability-info.ts +121 -0
  52. package/src/cli/commands/capability-list.ts +47 -0
  53. package/src/cli/commands/completion.ts +87 -0
  54. package/src/cli/commands/hook-run.ts +176 -0
  55. package/src/cli/commands/ipam.ts +607 -0
  56. package/src/cli/commands/machine-add.ts +235 -0
  57. package/src/cli/commands/machine-earmark.ts +82 -0
  58. package/src/cli/commands/machine-list.ts +77 -0
  59. package/src/cli/commands/machine-remove.ts +90 -0
  60. package/src/cli/commands/machine-status.ts +131 -0
  61. package/src/cli/commands/module-audit.ts +51 -0
  62. package/src/cli/commands/module-build.ts +60 -0
  63. package/src/cli/commands/module-config.ts +170 -0
  64. package/src/cli/commands/module-deploy.ts +71 -0
  65. package/src/cli/commands/module-generate.ts +236 -0
  66. package/src/cli/commands/module-health.ts +108 -0
  67. package/src/cli/commands/module-import.ts +80 -0
  68. package/src/cli/commands/module-list.ts +43 -0
  69. package/src/cli/commands/module-logs.ts +73 -0
  70. package/src/cli/commands/module-remove.ts +162 -0
  71. package/src/cli/commands/module-show.ts +208 -0
  72. package/src/cli/commands/module-status.ts +131 -0
  73. package/src/cli/commands/module-types.ts +189 -0
  74. package/src/cli/commands/module-upgrade.ts +192 -0
  75. package/src/cli/commands/package.ts +68 -0
  76. package/src/cli/commands/secret-list.ts +99 -0
  77. package/src/cli/commands/secret-set.ts +134 -0
  78. package/src/cli/commands/service-add-digitalocean.ts +133 -0
  79. package/src/cli/commands/service-add-proxmox.ts +342 -0
  80. package/src/cli/commands/service-config-get.ts +83 -0
  81. package/src/cli/commands/service-config-set.ts +145 -0
  82. package/src/cli/commands/service-list.ts +74 -0
  83. package/src/cli/commands/service-reconfigure.ts +230 -0
  84. package/src/cli/commands/service-remove.ts +103 -0
  85. package/src/cli/commands/service-verify.ts +240 -0
  86. package/src/cli/commands/status.ts +216 -0
  87. package/src/cli/commands/storage-add-local.ts +106 -0
  88. package/src/cli/commands/storage-add-s3.ts +114 -0
  89. package/src/cli/commands/storage-list.ts +72 -0
  90. package/src/cli/commands/storage-remove.ts +54 -0
  91. package/src/cli/commands/storage-set-default.ts +44 -0
  92. package/src/cli/commands/storage-verify.ts +54 -0
  93. package/src/cli/commands/system-config.ts +168 -0
  94. package/src/cli/commands/system-init.ts +314 -0
  95. package/src/cli/commands/system-secret-get.ts +98 -0
  96. package/src/cli/commands/system-secret-set.ts +76 -0
  97. package/src/cli/commands/system-vault-password.ts +34 -0
  98. package/src/cli/completion.test.ts +37 -0
  99. package/src/cli/completion.ts +482 -0
  100. package/src/cli/fuel-gauge.test.ts +208 -0
  101. package/src/cli/fuel-gauge.ts +405 -0
  102. package/src/cli/generate-zsh-completion.test.ts +95 -0
  103. package/src/cli/generate-zsh-completion.ts +497 -0
  104. package/src/cli/index.ts +1583 -0
  105. package/src/cli/interactive-config.test.ts +201 -0
  106. package/src/cli/interactive-config.ts +62 -0
  107. package/src/cli/parser.test.ts +227 -0
  108. package/src/cli/parser.ts +244 -0
  109. package/src/cli/prompts.test.ts +33 -0
  110. package/src/cli/prompts.ts +121 -0
  111. package/src/cli/types.ts +38 -0
  112. package/src/cli/validators.test.ts +235 -0
  113. package/src/cli/validators.ts +188 -0
  114. package/src/config/env.ts +41 -0
  115. package/src/config/paths.test.ts +172 -0
  116. package/src/config/paths.ts +108 -0
  117. package/src/db/client.ts +190 -0
  118. package/src/db/migrate.ts +30 -0
  119. package/src/db/schema.test.ts +221 -0
  120. package/src/db/schema.ts +434 -0
  121. package/src/hooks/capability-loader-firewall.test.ts +246 -0
  122. package/src/hooks/capability-loader.test.ts +100 -0
  123. package/src/hooks/capability-loader.ts +520 -0
  124. package/src/hooks/define-hook.test.ts +488 -0
  125. package/src/hooks/executor.test.ts +462 -0
  126. package/src/hooks/executor.ts +469 -0
  127. package/src/hooks/logger.test.ts +54 -0
  128. package/src/hooks/logger.ts +95 -0
  129. package/src/hooks/test-fixtures/failing-hook.ts +13 -0
  130. package/src/hooks/test-fixtures/no-default-hook.ts +6 -0
  131. package/src/hooks/test-fixtures/success-hook.ts +20 -0
  132. package/src/hooks/test-fixtures/unbranded-hook.ts +11 -0
  133. package/src/hooks/test-fixtures/void-hook.ts +13 -0
  134. package/src/hooks/types.ts +89 -0
  135. package/src/infrastructure/property-extractor.test.ts +194 -0
  136. package/src/infrastructure/property-extractor.ts +151 -0
  137. package/src/ipam/allocator.test.ts +442 -0
  138. package/src/ipam/allocator.ts +369 -0
  139. package/src/ipam/auto-allocator.test.ts +247 -0
  140. package/src/ipam/auto-allocator.ts +270 -0
  141. package/src/ipam/subnet-parser.test.ts +107 -0
  142. package/src/ipam/subnet-parser.ts +136 -0
  143. package/src/manifest/contracts/index.ts +61 -0
  144. package/src/manifest/contracts/v1.ts +118 -0
  145. package/src/manifest/json-schema-roundtrip.test.ts +99 -0
  146. package/src/manifest/schema.ts +367 -0
  147. package/src/manifest/template-validator.test.ts +231 -0
  148. package/src/manifest/template-validator.ts +322 -0
  149. package/src/manifest/validate.test.ts +1180 -0
  150. package/src/manifest/validate.ts +415 -0
  151. package/src/module/import.test.ts +355 -0
  152. package/src/module/import.ts +676 -0
  153. package/src/module/packaging/audit.ts +169 -0
  154. package/src/module/packaging/build.ts +228 -0
  155. package/src/module/packaging/checksum.ts +41 -0
  156. package/src/module/packaging/extract.ts +234 -0
  157. package/src/module/packaging/signature.ts +47 -0
  158. package/src/secrets/encryption.test.ts +284 -0
  159. package/src/secrets/encryption.ts +162 -0
  160. package/src/secrets/generators.test.ts +112 -0
  161. package/src/secrets/generators.ts +127 -0
  162. package/src/secrets/master-key.test.ts +159 -0
  163. package/src/secrets/master-key.ts +114 -0
  164. package/src/secrets/storage.test.ts +115 -0
  165. package/src/secrets/storage.ts +106 -0
  166. package/src/secrets/vault.test.ts +35 -0
  167. package/src/secrets/vault.ts +42 -0
  168. package/src/services/backup-create.ts +532 -0
  169. package/src/services/backup-metadata.ts +198 -0
  170. package/src/services/backup-restore.ts +229 -0
  171. package/src/services/backup-retention.ts +84 -0
  172. package/src/services/backup-storage.ts +281 -0
  173. package/src/services/build-stream.test.ts +122 -0
  174. package/src/services/build-stream.ts +201 -0
  175. package/src/services/config-interview.ts +694 -0
  176. package/src/services/container-service.test.ts +298 -0
  177. package/src/services/container-service.ts +401 -0
  178. package/src/services/cross-module-data-manager.test.ts +405 -0
  179. package/src/services/cross-module-data-manager.ts +412 -0
  180. package/src/services/deploy-ansible.ts +88 -0
  181. package/src/services/deploy-planner.ts +153 -0
  182. package/src/services/deploy-preflight.ts +274 -0
  183. package/src/services/deploy-ssh.ts +131 -0
  184. package/src/services/deploy-terraform.test.ts +55 -0
  185. package/src/services/deploy-terraform.ts +445 -0
  186. package/src/services/deploy-validation.ts +311 -0
  187. package/src/services/dns-auto-register.ts +211 -0
  188. package/src/services/health-runner.ts +184 -0
  189. package/src/services/infrastructure-selector.test.ts +485 -0
  190. package/src/services/infrastructure-selector.ts +245 -0
  191. package/src/services/infrastructure-variable-resolver.test.ts +751 -0
  192. package/src/services/infrastructure-variable-resolver.ts +234 -0
  193. package/src/services/machine-detector.ts +328 -0
  194. package/src/services/machine-pool.test.ts +405 -0
  195. package/src/services/machine-pool.ts +316 -0
  196. package/src/services/manifest-validation.ts +120 -0
  197. package/src/services/module-build.test.ts +290 -0
  198. package/src/services/module-build.ts +431 -0
  199. package/src/services/module-config.test.ts +237 -0
  200. package/src/services/module-config.ts +298 -0
  201. package/src/services/module-deploy.ts +862 -0
  202. package/src/services/module-types-drift.test.ts +73 -0
  203. package/src/services/module-types-generator.test.ts +288 -0
  204. package/src/services/module-types-generator.ts +189 -0
  205. package/src/services/proxmox-state-recovery.ts +140 -0
  206. package/src/services/schema-validation.ts +155 -0
  207. package/src/services/secret-schema-loader.test.ts +311 -0
  208. package/src/services/secret-schema-loader.ts +239 -0
  209. package/src/services/ssh-key-manager.test.ts +283 -0
  210. package/src/services/ssh-key-manager.ts +193 -0
  211. package/src/services/storage-providers/local.ts +105 -0
  212. package/src/services/storage-providers/s3.ts +182 -0
  213. package/src/services/storage-providers/types.ts +24 -0
  214. package/src/services/system-config-schema-types.ts +25 -0
  215. package/src/services/system-config-validator.test.ts +160 -0
  216. package/src/services/system-config-validator.ts +74 -0
  217. package/src/services/system-init.test.ts +153 -0
  218. package/src/services/system-init.ts +253 -0
  219. package/src/services/terraform-safety.ts +174 -0
  220. package/src/services/zone-detector.test.ts +110 -0
  221. package/src/services/zone-detector.ts +102 -0
  222. package/src/services/zone-policy.test.ts +97 -0
  223. package/src/services/zone-policy.ts +126 -0
  224. package/src/templates/generator.test.ts +645 -0
  225. package/src/templates/generator.ts +1119 -0
  226. package/src/templates/types.ts +62 -0
  227. package/src/test-utils/INTERACTIVE_PROMPTS.md +167 -0
  228. package/src/test-utils/cli-context-interactive.test.ts +152 -0
  229. package/src/test-utils/cli-context-server.test.ts +66 -0
  230. package/src/test-utils/cli-context.test.ts +273 -0
  231. package/src/test-utils/cli-context.ts +677 -0
  232. package/src/test-utils/cli-result.test.ts +282 -0
  233. package/src/test-utils/cli-result.ts +241 -0
  234. package/src/test-utils/cli.ts +55 -0
  235. package/src/test-utils/completion-harness.test.ts +126 -0
  236. package/src/test-utils/completion-harness.ts +82 -0
  237. package/src/test-utils/database.test.ts +182 -0
  238. package/src/test-utils/database.ts +126 -0
  239. package/src/test-utils/filesystem.test.ts +208 -0
  240. package/src/test-utils/filesystem.ts +142 -0
  241. package/src/test-utils/fixtures.test.ts +123 -0
  242. package/src/test-utils/fixtures.ts +160 -0
  243. package/src/test-utils/golden-diff.ts +197 -0
  244. package/src/test-utils/index.ts +77 -0
  245. package/src/test-utils/integration.ts +81 -0
  246. package/src/test-utils/module-fixtures.ts +468 -0
  247. package/src/test-utils/modules.test.ts +144 -0
  248. package/src/test-utils/modules.ts +183 -0
  249. package/src/test-utils/setup-test-db.ts +90 -0
  250. package/src/test-utils/value-extractor.test.ts +231 -0
  251. package/src/test-utils/value-extractor.ts +228 -0
  252. package/src/types/infrastructure.ts +157 -0
  253. package/src/utils/shell.test.ts +365 -0
  254. package/src/utils/shell.ts +159 -0
  255. package/src/validation/schemas.ts +166 -0
  256. package/src/variables/ansible-resolver.test.ts +142 -0
  257. package/src/variables/ansible-resolver.ts +69 -0
  258. package/src/variables/capability-self-ref.test.ts +220 -0
  259. package/src/variables/context.test.ts +1265 -0
  260. package/src/variables/context.ts +624 -0
  261. package/src/variables/declarative-derivation.test.ts +743 -0
  262. package/src/variables/declarative-derivation.ts +200 -0
  263. package/src/variables/parser.test.ts +231 -0
  264. package/src/variables/parser.ts +76 -0
  265. package/src/variables/resolver.test.ts +458 -0
  266. package/src/variables/resolver.ts +282 -0
  267. package/src/variables/types.ts +59 -0
@@ -0,0 +1,355 @@
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
2
+ import { existsSync } from 'node:fs';
3
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
4
+ import { join } from 'node:path';
5
+ import { eq } from 'drizzle-orm';
6
+ import { type DbClient, createDbClient } from '../db/client';
7
+ import { modules } from '../db/schema';
8
+ import type { ModuleManifest } from '../manifest/schema';
9
+ import {
10
+ copyModuleFiles,
11
+ importModule,
12
+ insertModuleToDb,
13
+ moduleExists,
14
+ readModuleManifest,
15
+ validateModuleDirectory,
16
+ } from './import';
17
+
18
+ const TEST_FIXTURES_DIR = './test-fixtures-temp'; // Temporary fixtures for this test (not the real golden files)
19
+ const TEST_TARGET_DIR = './test-modules-target';
20
+ const TEST_DB_PATH = './test-import.db';
21
+
22
+ describe('Module Import', () => {
23
+ let db: DbClient;
24
+
25
+ beforeEach(async () => {
26
+ // Create test database
27
+ db = createDbClient({ path: TEST_DB_PATH });
28
+
29
+ // Create tables
30
+ db.$client.run(`
31
+ CREATE TABLE IF NOT EXISTS modules (
32
+ id TEXT PRIMARY KEY,
33
+ name TEXT NOT NULL,
34
+ version TEXT NOT NULL,
35
+ description TEXT,
36
+ state TEXT NOT NULL DEFAULT 'IMPORTED',
37
+ manifest_data TEXT NOT NULL,
38
+ source_path TEXT NOT NULL,
39
+ imported_at INTEGER NOT NULL DEFAULT (unixepoch()),
40
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
41
+ error_message TEXT
42
+ )
43
+ `);
44
+
45
+ // Clean up test directories
46
+ if (existsSync(TEST_FIXTURES_DIR)) {
47
+ await rm(TEST_FIXTURES_DIR, { recursive: true });
48
+ }
49
+ if (existsSync(TEST_TARGET_DIR)) {
50
+ await rm(TEST_TARGET_DIR, { recursive: true });
51
+ }
52
+
53
+ await mkdir(TEST_FIXTURES_DIR, { recursive: true });
54
+ await mkdir(TEST_TARGET_DIR, { recursive: true });
55
+ });
56
+
57
+ afterEach(async () => {
58
+ db.$client.close();
59
+
60
+ // Clean up
61
+ if (existsSync(TEST_DB_PATH)) {
62
+ await rm(TEST_DB_PATH);
63
+ }
64
+ const walPath = `${TEST_DB_PATH}-wal`;
65
+ const shmPath = `${TEST_DB_PATH}-shm`;
66
+ if (existsSync(walPath)) {
67
+ await rm(walPath);
68
+ }
69
+ if (existsSync(shmPath)) {
70
+ await rm(shmPath);
71
+ }
72
+ if (existsSync(TEST_FIXTURES_DIR)) {
73
+ await rm(TEST_FIXTURES_DIR, { recursive: true });
74
+ }
75
+ if (existsSync(TEST_TARGET_DIR)) {
76
+ await rm(TEST_TARGET_DIR, { recursive: true });
77
+ }
78
+ });
79
+
80
+ describe('validateModuleDirectory', () => {
81
+ test('should return null for valid module directory', async () => {
82
+ const moduleDir = join(TEST_FIXTURES_DIR, 'valid-module');
83
+ await mkdir(moduleDir, { recursive: true });
84
+ await writeFile(join(moduleDir, 'manifest.yml'), 'id: test');
85
+
86
+ const result = validateModuleDirectory(moduleDir);
87
+
88
+ expect(result).toBeNull();
89
+ });
90
+
91
+ test('should return error for non-existent directory', () => {
92
+ const result = validateModuleDirectory('./does-not-exist');
93
+
94
+ expect(result).toContain('does not exist');
95
+ });
96
+
97
+ test('should return error for file instead of directory', async () => {
98
+ const filePath = join(TEST_FIXTURES_DIR, 'not-a-dir');
99
+ await writeFile(filePath, 'content');
100
+
101
+ const result = validateModuleDirectory(filePath);
102
+
103
+ expect(result).toContain('not a directory');
104
+ });
105
+
106
+ test('should return error if manifest.yml missing', async () => {
107
+ const moduleDir = join(TEST_FIXTURES_DIR, 'no-manifest');
108
+ await mkdir(moduleDir, { recursive: true });
109
+
110
+ const result = validateModuleDirectory(moduleDir);
111
+
112
+ expect(result).toContain('manifest.yml not found');
113
+ });
114
+ });
115
+
116
+ describe('readModuleManifest', () => {
117
+ test('should read and validate valid manifest', async () => {
118
+ const moduleDir = join(TEST_FIXTURES_DIR, 'homebridge');
119
+ await mkdir(moduleDir, { recursive: true });
120
+ await writeFile(
121
+ join(moduleDir, 'manifest.yml'),
122
+ `
123
+ celilo_contract: "1.0"
124
+ id: homebridge
125
+ name: Homebridge
126
+ version: 1.0.0
127
+ description: HomeKit bridge
128
+ `,
129
+ );
130
+
131
+ const result = await readModuleManifest(moduleDir);
132
+
133
+ expect(result.success).toBe(true);
134
+ if (result.success) {
135
+ expect(result.manifest.id).toBe('homebridge');
136
+ expect(result.manifest.name).toBe('Homebridge');
137
+ expect(result.manifest.version).toBe('1.0.0');
138
+ }
139
+ });
140
+
141
+ test('should return error for invalid manifest', async () => {
142
+ const moduleDir = join(TEST_FIXTURES_DIR, 'invalid');
143
+ await mkdir(moduleDir, { recursive: true });
144
+ await writeFile(
145
+ join(moduleDir, 'manifest.yml'),
146
+ `
147
+ id: Invalid_ID
148
+ name: Test
149
+ version: 1.0.0
150
+ `,
151
+ );
152
+
153
+ const result = await readModuleManifest(moduleDir);
154
+
155
+ expect(result.success).toBe(false);
156
+ if (!result.success) {
157
+ expect(result.error).toContain('validation failed');
158
+ }
159
+ });
160
+
161
+ test('should return error for missing manifest file', async () => {
162
+ const moduleDir = join(TEST_FIXTURES_DIR, 'no-file');
163
+ await mkdir(moduleDir, { recursive: true });
164
+
165
+ const result = await readModuleManifest(moduleDir);
166
+
167
+ expect(result.success).toBe(false);
168
+ if (!result.success) {
169
+ expect(result.error).toContain('Failed to read');
170
+ }
171
+ });
172
+ });
173
+
174
+ describe('copyModuleFiles', () => {
175
+ test('should copy all files from source to target', async () => {
176
+ const sourceDir = join(TEST_FIXTURES_DIR, 'source');
177
+ const targetDir = join(TEST_TARGET_DIR, 'target');
178
+
179
+ await mkdir(sourceDir, { recursive: true });
180
+ await writeFile(join(sourceDir, 'manifest.yml'), 'content1');
181
+ await writeFile(join(sourceDir, 'README.md'), 'content2');
182
+ await mkdir(join(sourceDir, 'terraform'), { recursive: true });
183
+ await writeFile(join(sourceDir, 'terraform', 'main.tf'), 'terraform content');
184
+
185
+ await copyModuleFiles(sourceDir, targetDir);
186
+
187
+ expect(existsSync(join(targetDir, 'manifest.yml'))).toBe(true);
188
+ expect(existsSync(join(targetDir, 'README.md'))).toBe(true);
189
+ expect(existsSync(join(targetDir, 'terraform', 'main.tf'))).toBe(true);
190
+ });
191
+
192
+ test('should create target directory if it does not exist', async () => {
193
+ const sourceDir = join(TEST_FIXTURES_DIR, 'source2');
194
+ const targetDir = join(TEST_TARGET_DIR, 'nested', 'deep', 'target');
195
+
196
+ await mkdir(sourceDir, { recursive: true });
197
+ await writeFile(join(sourceDir, 'file.txt'), 'content');
198
+
199
+ await copyModuleFiles(sourceDir, targetDir);
200
+
201
+ expect(existsSync(targetDir)).toBe(true);
202
+ expect(existsSync(join(targetDir, 'file.txt'))).toBe(true);
203
+ });
204
+ });
205
+
206
+ describe('insertModuleToDb', () => {
207
+ test('should insert module record into database', async () => {
208
+ const manifest: ModuleManifest = {
209
+ celilo_contract: '1.0',
210
+ id: 'test-module',
211
+ name: 'Test Module',
212
+ version: '1.0.0',
213
+ requires: { capabilities: [] },
214
+ provides: { capabilities: [] },
215
+ variables: { owns: [], imports: [] },
216
+ };
217
+
218
+ await insertModuleToDb(manifest, '/target/path', db);
219
+
220
+ const result = db.select().from(modules).where(eq(modules.id, 'test-module')).get();
221
+
222
+ expect(result).toBeDefined();
223
+ expect(result?.name).toBe('Test Module');
224
+ expect(result?.version).toBe('1.0.0');
225
+ expect(result?.state).toBe('IMPORTED');
226
+ expect(result?.sourcePath).toBe('/target/path');
227
+ });
228
+ });
229
+
230
+ describe('moduleExists', () => {
231
+ test('should return true for existing module', () => {
232
+ db.insert(modules)
233
+ .values({
234
+ id: 'existing',
235
+ name: 'Existing',
236
+ version: '1.0.0',
237
+ sourcePath: '/path',
238
+ manifestData: {},
239
+ })
240
+ .run();
241
+
242
+ const result = moduleExists('existing', db);
243
+
244
+ expect(result).toBe(true);
245
+ });
246
+
247
+ test('should return false for non-existent module', () => {
248
+ const result = moduleExists('does-not-exist', db);
249
+
250
+ expect(result).toBe(false);
251
+ });
252
+ });
253
+
254
+ describe('importModule', () => {
255
+ test('should successfully import valid module', async () => {
256
+ const moduleDir = join(TEST_FIXTURES_DIR, 'homebridge');
257
+ await mkdir(moduleDir, { recursive: true });
258
+ await mkdir(join(moduleDir, 'terraform'), { recursive: true });
259
+ await writeFile(
260
+ join(moduleDir, 'manifest.yml'),
261
+ `
262
+ celilo_contract: "1.0"
263
+ id: homebridge
264
+ name: Homebridge
265
+ version: 1.0.0
266
+ `,
267
+ );
268
+ await writeFile(join(moduleDir, 'README.md'), '# Homebridge');
269
+ await writeFile(join(moduleDir, 'terraform', 'main.tf'), 'resource "test" {}');
270
+
271
+ const result = await importModule({
272
+ sourcePath: moduleDir,
273
+ targetBasePath: TEST_TARGET_DIR,
274
+ db,
275
+ });
276
+
277
+ expect(result.success).toBe(true);
278
+ if (result.success) {
279
+ expect(result.moduleId).toBe('homebridge');
280
+ expect(existsSync(result.targetPath)).toBe(true);
281
+ expect(existsSync(join(result.targetPath, 'manifest.yml'))).toBe(true);
282
+ expect(existsSync(join(result.targetPath, 'terraform', 'main.tf'))).toBe(true);
283
+ }
284
+
285
+ // Verify database
286
+ const dbRecord = db.select().from(modules).where(eq(modules.id, 'homebridge')).get();
287
+ expect(dbRecord).toBeDefined();
288
+ expect(dbRecord?.name).toBe('Homebridge');
289
+ });
290
+
291
+ test('should reject import if directory does not exist', async () => {
292
+ const result = await importModule({
293
+ sourcePath: './does-not-exist',
294
+ targetBasePath: TEST_TARGET_DIR,
295
+ db,
296
+ });
297
+
298
+ expect(result.success).toBe(false);
299
+ if (!result.success) {
300
+ expect(result.error).toContain('does not exist');
301
+ }
302
+ });
303
+
304
+ test('should reject import if manifest is invalid', async () => {
305
+ const moduleDir = join(TEST_FIXTURES_DIR, 'invalid');
306
+ await mkdir(moduleDir, { recursive: true });
307
+ await writeFile(
308
+ join(moduleDir, 'manifest.yml'),
309
+ 'id: Invalid_ID\nname: Test\nversion: 1.0.0',
310
+ );
311
+
312
+ const result = await importModule({
313
+ sourcePath: moduleDir,
314
+ targetBasePath: TEST_TARGET_DIR,
315
+ db,
316
+ });
317
+
318
+ expect(result.success).toBe(false);
319
+ if (!result.success) {
320
+ expect(result.error).toContain('validation failed');
321
+ }
322
+ });
323
+
324
+ test('should reject import if module already exists', async () => {
325
+ // Insert existing module
326
+ db.insert(modules)
327
+ .values({
328
+ id: 'existing',
329
+ name: 'Existing',
330
+ version: '1.0.0',
331
+ sourcePath: '/path',
332
+ manifestData: {},
333
+ })
334
+ .run();
335
+
336
+ const moduleDir = join(TEST_FIXTURES_DIR, 'existing');
337
+ await mkdir(moduleDir, { recursive: true });
338
+ await writeFile(
339
+ join(moduleDir, 'manifest.yml'),
340
+ 'celilo_contract: "1.0"\nid: existing\nname: Existing\nversion: 2.0.0',
341
+ );
342
+
343
+ const result = await importModule({
344
+ sourcePath: moduleDir,
345
+ targetBasePath: TEST_TARGET_DIR,
346
+ db,
347
+ });
348
+
349
+ expect(result.success).toBe(false);
350
+ if (!result.success) {
351
+ expect(result.error).toContain('already exists');
352
+ }
353
+ });
354
+ });
355
+ });