@gravito/launchpad 1.2.1 → 1.3.1

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.
@@ -14,8 +14,18 @@ export class DockerAdapter implements IDockerAdapter {
14
14
  private baseImage = 'oven/bun:1.0-slim'
15
15
  private runtime = getRuntimeAdapter()
16
16
 
17
+ // 快取目錄配置
18
+ private readonly cacheConfig = {
19
+ hostCachePath: process.env.BUN_CACHE_PATH || `${process.env.HOME}/.bun/install/cache`,
20
+ containerCachePathRoot: '/root/.bun/install/cache',
21
+ containerCachePathBun: '/home/bun/.bun/install/cache',
22
+ }
23
+
17
24
  async createBaseContainer(): Promise<string> {
18
- const rocketId = `rocket-${Math.random().toString(36).substring(2, 9)}`
25
+ const rocketId = `rocket-${crypto.randomUUID()}`
26
+
27
+ // 確保宿主機快取目錄存在
28
+ await this.ensureCacheDirectory()
19
29
 
20
30
  const proc = this.runtime.spawn([
21
31
  'docker',
@@ -27,10 +37,39 @@ export class DockerAdapter implements IDockerAdapter {
27
37
  'gravito-origin=launchpad',
28
38
  '-p',
29
39
  '3000', // 讓 Docker 分配隨機宿主機埠
40
+ // === 快取掛載(關鍵) ===
30
41
  '-v',
31
- `${process.env.HOME}/.bun/install/cache:/root/.bun/install/cache`,
42
+ `${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathRoot}:rw`,
32
43
  '-v',
33
- `${process.env.HOME}/.bun/install/cache:/home/bun/.bun/install/cache`,
44
+ `${this.cacheConfig.hostCachePath}:${this.cacheConfig.containerCachePathBun}:rw`,
45
+ // === 環境變數配置(確保 bun 使用快取) ===
46
+ '-e',
47
+ `BUN_INSTALL_CACHE_DIR=${this.cacheConfig.containerCachePathBun}`,
48
+ '-e',
49
+ 'BUN_INSTALL_CACHE=shared',
50
+ '-e',
51
+ 'BUN_INSTALL_PREFER_OFFLINE=true',
52
+ // === 效能相關環境變數 ===
53
+ '-e',
54
+ 'NODE_ENV=development',
55
+ // === 資源限制(防止單一容器占用過多資源) ===
56
+ '--memory',
57
+ '1g',
58
+ '--memory-swap',
59
+ '1g',
60
+ '--cpus',
61
+ '1.0',
62
+ // === 安全設定 ===
63
+ '--security-opt',
64
+ 'no-new-privileges:true',
65
+ '--cap-drop',
66
+ 'ALL',
67
+ '--cap-add',
68
+ 'CHOWN',
69
+ '--cap-add',
70
+ 'SETUID',
71
+ '--cap-add',
72
+ 'SETGID',
34
73
  this.baseImage,
35
74
  'tail',
36
75
  '-f',
@@ -42,6 +81,8 @@ export class DockerAdapter implements IDockerAdapter {
42
81
  const exitCode = await proc.exited
43
82
 
44
83
  if (containerId.length === 64 && /^[0-9a-f]+$/.test(containerId)) {
84
+ // 驗證快取是否正確掛載
85
+ await this.verifyCacheMount(containerId)
45
86
  return containerId
46
87
  }
47
88
 
@@ -53,6 +94,42 @@ export class DockerAdapter implements IDockerAdapter {
53
94
  return containerId
54
95
  }
55
96
 
97
+ /**
98
+ * 確保快取目錄存在
99
+ *
100
+ * @private
101
+ * @since 1.3.0
102
+ */
103
+ private async ensureCacheDirectory(): Promise<void> {
104
+ const cachePath = this.cacheConfig.hostCachePath
105
+ const proc = this.runtime.spawn(['mkdir', '-p', cachePath])
106
+ await proc.exited
107
+
108
+ // 設定適當的權限
109
+ const chmodProc = this.runtime.spawn(['chmod', '777', cachePath])
110
+ await chmodProc.exited
111
+ }
112
+
113
+ /**
114
+ * 驗證快取是否正確掛載
115
+ *
116
+ * @private
117
+ * @since 1.3.0
118
+ */
119
+ private async verifyCacheMount(containerId: string): Promise<void> {
120
+ const result = await this.executeCommand(containerId, [
121
+ 'sh',
122
+ '-c',
123
+ `ls -la ${this.cacheConfig.containerCachePathBun} 2>/dev/null || echo 'NOT_MOUNTED'`,
124
+ ])
125
+
126
+ if (result.stdout.includes('NOT_MOUNTED')) {
127
+ console.warn(`[DockerAdapter] 警告: 容器 ${containerId} 的快取目錄可能未正確掛載`)
128
+ } else {
129
+ console.log(`[DockerAdapter] 快取目錄已確認掛載: ${containerId}`)
130
+ }
131
+ }
132
+
56
133
  async getExposedPort(containerId: string, containerPort = 3000): Promise<number> {
57
134
  const proc = this.runtime.spawn(['docker', 'port', containerId, containerPort.toString()])
58
135
  const stdout = await new Response(proc.stdout ?? null).text()
@@ -15,7 +15,7 @@ export class ShellGitAdapter implements IGitAdapter {
15
15
  private baseDir = '/tmp/gravito-launchpad-git'
16
16
 
17
17
  async clone(repoUrl: string, branch: string): Promise<string> {
18
- const dirName = `${Date.now()}-${Math.random().toString(36).slice(2)}`
18
+ const dirName = `${Date.now()}-${crypto.randomUUID()}`
19
19
  const targetDir = `${this.baseDir}/${dirName}`
20
20
 
21
21
  await mkdir(this.baseDir, { recursive: true })
package/src/index.ts CHANGED
@@ -13,9 +13,12 @@ import { CachedRocketRepository } from './Infrastructure/Persistence/CachedRocke
13
13
  import { BunProxyAdapter } from './Infrastructure/Router/BunProxyAdapter'
14
14
 
15
15
  export * from './Application/MissionControl'
16
+ export * from './Application/MissionQueue'
16
17
  export * from './Application/PayloadInjector'
17
18
  export * from './Application/PoolManager'
18
19
  export * from './Application/RefurbishUnit'
20
+ export * from './Domain/Events'
21
+ export * from './Domain/Interfaces'
19
22
  export * from './Domain/Mission'
20
23
  export * from './Domain/Rocket'
21
24
  export * from './Domain/RocketStatus'
@@ -23,7 +23,9 @@ describe('Payload Injection Flow', () => {
23
23
  findAll: mock(() => Promise.resolve([])),
24
24
  }
25
25
 
26
- const pool = new PoolManager(mockDocker, mockRepo)
26
+ const pool = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
27
+ exhaustionStrategy: 'dynamic',
28
+ })
27
29
  const injector = new PayloadInjector(mockDocker, mockGit)
28
30
 
29
31
  it('應該能從指派任務到成功點火', async () => {
@@ -39,7 +39,9 @@ describe('PoolManager', () => {
39
39
  test('assigns missions to idle rockets or creates new ones', async () => {
40
40
  const docker = new FakeDocker()
41
41
  const repo = new InMemoryRocketRepository()
42
- const pool = new PoolManager(docker as any, repo)
42
+ const pool = new PoolManager(docker as any, repo, undefined, undefined, {
43
+ exhaustionStrategy: 'dynamic',
44
+ })
43
45
 
44
46
  const mission = Mission.create({
45
47
  id: 'mission-2',
@@ -0,0 +1,241 @@
1
+ import { beforeEach, describe, expect, mock, test } from 'bun:test'
2
+ import { MissionQueue, PoolExhaustedException } from '../src/Application/MissionQueue'
3
+ import { PoolManager } from '../src/Application/PoolManager'
4
+ import { Mission } from '../src/Domain/Mission'
5
+ import type { Rocket } from '../src/Domain/Rocket'
6
+ import { RocketStatus } from '../src/Domain/RocketStatus'
7
+
8
+ describe('MissionQueue', () => {
9
+ let queue: MissionQueue
10
+
11
+ beforeEach(() => {
12
+ queue = new MissionQueue(3, 1000)
13
+ })
14
+
15
+ test('should enqueue missions and track length', () => {
16
+ const mission = Mission.create({
17
+ id: 'pr-1',
18
+ repoUrl: 'https://github.com/test/repo',
19
+ branch: 'main',
20
+ commitSha: 'abc123',
21
+ })
22
+
23
+ const promise = queue.enqueue(mission)
24
+ expect(queue.length).toBe(1)
25
+ expect(promise).toBeInstanceOf(Promise)
26
+
27
+ // 清理:dequeue 或 catch 錯誤以避免 unhandled rejection
28
+ promise.catch(() => {})
29
+ })
30
+
31
+ test('should reject when queue is full', async () => {
32
+ const promises = []
33
+ for (let i = 0; i < 3; i++) {
34
+ const promise = queue.enqueue(
35
+ Mission.create({
36
+ id: `pr-${i}`,
37
+ repoUrl: 'https://github.com/test/repo',
38
+ branch: 'main',
39
+ commitSha: 'abc123',
40
+ })
41
+ )
42
+ // 捕獲超時錯誤以避免 unhandled rejection
43
+ promise.catch(() => {})
44
+ promises.push(promise)
45
+ }
46
+
47
+ const mission = Mission.create({
48
+ id: 'pr-4',
49
+ repoUrl: 'https://github.com/test/repo',
50
+ branch: 'main',
51
+ commitSha: 'abc123',
52
+ })
53
+
54
+ await expect(queue.enqueue(mission)).rejects.toThrow(PoolExhaustedException)
55
+ })
56
+
57
+ test('should dequeue in FIFO order', () => {
58
+ const m1 = Mission.create({
59
+ id: 'pr-1',
60
+ repoUrl: 'https://github.com/test/repo',
61
+ branch: 'main',
62
+ commitSha: 'abc123',
63
+ })
64
+ const m2 = Mission.create({
65
+ id: 'pr-2',
66
+ repoUrl: 'https://github.com/test/repo',
67
+ branch: 'main',
68
+ commitSha: 'abc123',
69
+ })
70
+
71
+ const p1 = queue.enqueue(m1)
72
+ const p2 = queue.enqueue(m2)
73
+
74
+ const first = queue.dequeue()
75
+ expect(first?.mission.id).toBe('pr-1')
76
+
77
+ const second = queue.dequeue()
78
+ expect(second?.mission.id).toBe('pr-2')
79
+
80
+ const empty = queue.dequeue()
81
+ expect(empty).toBeNull()
82
+
83
+ // 清理未使用的 promises
84
+ p1.catch(() => {})
85
+ p2.catch(() => {})
86
+ })
87
+
88
+ test('should provide stats', async () => {
89
+ const promise = queue.enqueue(
90
+ Mission.create({
91
+ id: 'pr-1',
92
+ repoUrl: 'https://github.com/test/repo',
93
+ branch: 'main',
94
+ commitSha: 'abc123',
95
+ })
96
+ )
97
+
98
+ // 等待一點時間讓時間戳有差異
99
+ await new Promise((resolve) => setTimeout(resolve, 10))
100
+
101
+ const stats = queue.getStats()
102
+ expect(stats.length).toBe(1)
103
+ expect(stats.maxSize).toBe(3)
104
+ expect(stats.oldestWaitMs).toBeGreaterThanOrEqual(0)
105
+
106
+ // 清理
107
+ promise.catch(() => {})
108
+ })
109
+ })
110
+
111
+ describe('PoolManager Resource Limits', () => {
112
+ let mockDocker: any
113
+ let mockRepo: any
114
+ let rockets: Rocket[]
115
+
116
+ beforeEach(() => {
117
+ rockets = []
118
+ mockDocker = {
119
+ createBaseContainer: mock(() => Promise.resolve(`container-${Date.now()}`)),
120
+ removeContainer: mock(() => Promise.resolve()),
121
+ executeCommand: mock(() => Promise.resolve({ exitCode: 0, stdout: '', stderr: '' })),
122
+ getExposedPort: mock(() => Promise.resolve(3000)),
123
+ removeContainerByLabel: mock(() => Promise.resolve()),
124
+ streamLogs: mock(() => {}),
125
+ getStats: mock(() => Promise.resolve({ cpu: '0%', memory: '0MB' })),
126
+ }
127
+ mockRepo = {
128
+ save: mock((r: Rocket) => {
129
+ const index = rockets.findIndex((rocket) => rocket.id === r.id)
130
+ if (index >= 0) {
131
+ rockets[index] = r
132
+ } else {
133
+ rockets.push(r)
134
+ }
135
+ return Promise.resolve()
136
+ }),
137
+ findAll: mock(() => Promise.resolve(rockets)),
138
+ findIdle: mock(() =>
139
+ Promise.resolve(rockets.find((r) => r.status === RocketStatus.IDLE) || null)
140
+ ),
141
+ findById: mock((id: string) => Promise.resolve(rockets.find((r) => r.id === id) || null)),
142
+ delete: mock(() => Promise.resolve()),
143
+ }
144
+ })
145
+
146
+ test('should reject when maxRockets reached with reject strategy', async () => {
147
+ const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
148
+ maxRockets: 2,
149
+ exhaustionStrategy: 'reject',
150
+ warmupCount: 2,
151
+ })
152
+
153
+ await manager.warmup()
154
+ expect(rockets.length).toBe(2)
155
+
156
+ // 使用全部火箭
157
+ const m1 = Mission.create({
158
+ id: 'pr-1',
159
+ repoUrl: 'https://github.com/test/repo',
160
+ branch: 'main',
161
+ commitSha: 'abc1',
162
+ })
163
+ const m2 = Mission.create({
164
+ id: 'pr-2',
165
+ repoUrl: 'https://github.com/test/repo',
166
+ branch: 'main',
167
+ commitSha: 'abc2',
168
+ })
169
+
170
+ await manager.assignMission(m1)
171
+ await manager.assignMission(m2)
172
+
173
+ // 第三個請求應該被拒絕
174
+ const m3 = Mission.create({
175
+ id: 'pr-3',
176
+ repoUrl: 'https://github.com/test/repo',
177
+ branch: 'main',
178
+ commitSha: 'abc3',
179
+ })
180
+
181
+ await expect(manager.assignMission(m3)).rejects.toThrow(PoolExhaustedException)
182
+ })
183
+
184
+ test('should respect maxRockets limit', async () => {
185
+ const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
186
+ maxRockets: 5,
187
+ warmupCount: 3,
188
+ })
189
+
190
+ await manager.warmup()
191
+
192
+ const status = await manager.getPoolStatus()
193
+ expect(status.total).toBe(3)
194
+ expect(status.idle).toBe(3)
195
+ expect(status.maxRockets).toBe(5)
196
+ })
197
+
198
+ test('should queue missions when pool is exhausted', async () => {
199
+ const manager = new PoolManager(mockDocker, mockRepo, undefined, undefined, {
200
+ maxRockets: 1,
201
+ exhaustionStrategy: 'queue',
202
+ queueTimeoutMs: 5000,
203
+ warmupCount: 1,
204
+ })
205
+
206
+ await manager.warmup()
207
+
208
+ const m1 = Mission.create({
209
+ id: 'pr-1',
210
+ repoUrl: 'https://github.com/test/repo',
211
+ branch: 'main',
212
+ commitSha: 'abc1',
213
+ })
214
+
215
+ const rocket1 = await manager.assignMission(m1)
216
+ expect(rocket1.status).toBe(RocketStatus.PREPARING)
217
+
218
+ // 第二個任務應該進入隊列
219
+ const m2 = Mission.create({
220
+ id: 'pr-2',
221
+ repoUrl: 'https://github.com/test/repo',
222
+ branch: 'main',
223
+ commitSha: 'abc2',
224
+ })
225
+
226
+ const promise = manager.assignMission(m2)
227
+
228
+ // 等待 promise 開始處理
229
+ await new Promise((resolve) => setTimeout(resolve, 10))
230
+
231
+ const stats = manager.getQueueStats()
232
+ expect(stats.length).toBe(1)
233
+
234
+ // 模擬回收
235
+ rocket1.ignite()
236
+ await manager.recycle('pr-1')
237
+
238
+ const rocket2 = await promise
239
+ expect(rocket2.currentMission?.id).toBe('pr-2')
240
+ })
241
+ })
package/tsconfig.json CHANGED
@@ -1,27 +1,15 @@
1
1
  {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "skipLibCheck": true,
6
- "types": [
7
- "bun-types"
8
- ],
9
- "baseUrl": ".",
10
- "paths": {
11
- "@gravito/core": [
12
- "../../packages/core/src/index.ts"
13
- ],
14
- "@gravito/*": [
15
- "../../packages/*/src/index.ts"
16
- ]
17
- }
18
- },
19
- "include": [
20
- "src/**/*"
21
- ],
22
- "exclude": [
23
- "node_modules",
24
- "dist",
25
- "**/*.test.ts"
26
- ]
27
- }
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "skipLibCheck": true,
6
+ "types": ["bun-types"],
7
+ "baseUrl": ".",
8
+ "paths": {
9
+ "@gravito/core": ["../../packages/core/src/index.ts"],
10
+ "@gravito/*": ["../../packages/*/src/index.ts"]
11
+ }
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
15
+ }
@@ -1,18 +0,0 @@
1
-
2
- $ tsup src/index.ts --format cjs,esm --dts
3
- CLI Building entry: src/index.ts
4
- CLI Using tsconfig: tsconfig.json
5
- CLI tsup v8.5.1
6
- CLI Target: esnext
7
- CJS Build start
8
- ESM Build start
9
- CJS You have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin
10
- ESM You have emitDecoratorMetadata enabled but @swc/core was not installed, skipping swc plugin
11
- CJS dist/index.js 27.01 KB
12
- CJS ⚡️ Build success in 24ms
13
- ESM dist/index.mjs 25.50 KB
14
- ESM ⚡️ Build success in 24ms
15
- DTS Build start
16
- DTS ⚡️ Build success in 1979ms
17
- DTS dist/index.d.ts 5.14 KB
18
- DTS dist/index.d.mts 5.14 KB
@@ -1,183 +0,0 @@
1
- $ bun test --coverage --coverage-threshold=80
2
- bun test v1.3.4 (5eb2145b)
3
-
4
- tests/mission.test.ts:
5
- (pass) Mission > exposes mission properties [0.31ms]
6
-
7
- tests/Telemetry.test.ts:
8
- [MissionControl] 準備發射任務: pr-1
9
- [MissionControl] 任務 pr-1 映射端口: 3000
10
- (pass) MissionControl Telemetry > 發射時應該能正確啟動日誌串流與效能監控 [6.79ms]
11
-
12
- tests/mission-control.test.ts:
13
- [MissionControl] 準備發射任務: mission-telemetry
14
- [MissionControl] 任務 mission-telemetry 映射端口: 3000
15
- [MissionControl] 任務 mission-telemetry TTL 已到期,執行自動回收...
16
- (pass) MissionControl timers > emits stats and schedules recycle [2.17ms]
17
-
18
- tests/pool-manager.test.ts:
19
- [LaunchPad] 正在熱機,準備發射 2 架新火箭...
20
- (pass) PoolManager > warms up the pool with base containers [1.23ms]
21
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
22
- [LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
23
- (pass) PoolManager > assigns missions to idle rockets or creates new ones [1.90ms]
24
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
25
- (pass) PoolManager > recycles rockets through refurbish flow [2.99ms]
26
-
27
- tests/enterprise-coverage.test.ts:
28
- (pass) Launchpad enterprise coverage > covers core enterprise primitives [0.56ms]
29
-
30
- tests/repository.test.ts:
31
- (pass) InMemoryRocketRepository > stores and retrieves rockets [0.75ms]
32
- (pass) InMemoryRocketRepository > finds idle rockets [0.19ms]
33
-
34
- tests/Rocket.test.ts:
35
- (pass) Rocket > transitions through mission lifecycle and emits events [0.17ms]
36
- (pass) Rocket > guards invalid transitions [0.19ms]
37
- (pass) Rocket > assigns domain and serializes/deserializes [0.15ms]
38
- (pass) Rocket > rejects assigning mission when not idle [0.06ms]
39
-
40
- tests/Refurbishment.test.ts:
41
- (pass) RefurbishUnit (Rocket Recovery) > 應該能執行深度清理並將火箭重置為 IDLE [1.11ms]
42
- [RefurbishUnit] 正在翻新火箭: r1 (容器: c1)
43
- [RefurbishUnit] 火箭 r1 翻新完成,已進入 IDLE 狀態。
44
- [RefurbishUnit] 正在翻新火箭: r2 (容器: c2)
45
- [RefurbishUnit] 清理失敗: Disk Full
46
- (pass) RefurbishUnit (Rocket Recovery) > 如果清理失敗應該將火箭除役 [0.28ms]
47
-
48
- tests/payload-injector.test.ts:
49
- (pass) PayloadInjector > throws when rocket has no mission [1.02ms]
50
- [PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
51
- [PayloadInjector] 正在注入載荷至容器: container-10
52
- [PayloadInjector] 正在安裝依賴...
53
- [PayloadInjector] 點火!
54
- (pass) PayloadInjector > deploys payload and ignites rocket [0.78ms]
55
- [PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
56
- [PayloadInjector] 正在注入載荷至容器: container-11
57
- [PayloadInjector] 正在安裝依賴...
58
- (pass) PayloadInjector > throws when install fails [4.37ms]
59
- [PayloadInjector] 正在拉取代碼: https://example.com/repo.git (main)
60
- [PayloadInjector] 正在注入載荷至容器: container-12
61
- [PayloadInjector] 正在安裝依賴...
62
- [PayloadInjector] 點火!
63
- (pass) PayloadInjector > logs when run command fails but still ignites [8.24ms]
64
-
65
- tests/docker-adapter.test.ts:
66
- (pass) DockerAdapter > creates base container when stdout has container id [8.90ms]
67
- (pass) DockerAdapter > returns container id even when exit code is zero but id is invalid [0.28ms]
68
- (pass) DockerAdapter > throws when container creation fails [0.70ms]
69
- (pass) DockerAdapter > getExposedPort parses the first line [1.64ms]
70
- (pass) DockerAdapter > getExposedPort throws on empty output [6.46ms]
71
- (pass) DockerAdapter > getExposedPort throws on invalid output [4.70ms]
72
- (pass) DockerAdapter > copyFiles throws on non-zero exit code [0.54ms]
73
- (pass) DockerAdapter > removeContainerByLabel removes containers when ids exist [0.72ms]
74
- (pass) DockerAdapter > executeCommand returns stdout and stderr [3.41ms]
75
- (pass) DockerAdapter > removeContainer executes docker rm [1.66ms]
76
- (pass) DockerAdapter > getStats parses cpu and memory output [0.39ms]
77
- (pass) DockerAdapter > streamLogs forwards stdout and stderr [43.53ms]
78
-
79
- tests/Integration.test.ts:
80
- [LaunchPad] 正在熱機,準備發射 1 架新火箭...
81
- [Test] 容器內 Bun 版本: 1.0.36
82
- (pass) LaunchPad 集成測試 (真實 Docker) > 應該能成功熱機並在容器內執行指令 [3201.00ms]
83
-
84
- tests/Deployment.test.ts:
85
- [LaunchPad] 資源吃緊,正在緊急呼叫後援火箭...
86
- [PayloadInjector] 正在拉取代碼: http://git (dev)
87
- [PayloadInjector] 正在注入載荷至容器: cid-1
88
- [PayloadInjector] 正在安裝依賴...
89
- [PayloadInjector] 點火!
90
- (pass) Payload Injection Flow > 應該能從指派任務到成功點火 [1.02ms]
91
-
92
- tests/PoolManager.test.ts:
93
- (pass) PoolManager (Application Service) > 應該能正確熱機並指派任務 [1.54ms]
94
- ------------------------------------------------------------|---------|---------|-------------------
95
- File | % Funcs | % Lines | Uncovered Line #s
96
- [LaunchPad] 正在熱機,準備發射 2 架新火箭...
97
- ------------------------------------------------------------|---------|---------|-------------------
98
- All files | 30.25 | 42.00 |
99
- ../core/src/Application.ts | 0.00 | 8.87 | 139-313,325-326,336-337,348,358,368,375,382
100
- ../core/src/ConfigManager.ts | 0.00 | 14.04 | 16-22,29-34,42-75,82
101
- ../core/src/Container.ts | 0.00 | 28.95 | 26,34,41,48-67,74,81-82
102
- ../core/src/ErrorHandler.ts | 0.00 | 3.45 | 23-41,49-75,101-297,304-325,332-357,365-381
103
- ../core/src/Event.ts | 100.00 | 100.00 |
104
- ../core/src/EventManager.ts | 0.00 | 7.01 | 78,85,108-126,136-243,250-256,263-271
105
- ../core/src/GlobalErrorHandlers.ts | 0.00 | 1.19 | 58-72,78-88,92-101,106-186,191-210,214-228,239-252
106
- ../core/src/GravitoServer.ts | 0.00 | 8.77 | 25-76
107
- ../core/src/HookManager.ts | 0.00 | 8.33 | 37-73,93-121
108
- ../core/src/Logger.ts | 0.00 | 55.56 | 17,21,25
109
- ../core/src/PlanetCore.ts | 0.00 | 10.53 | 117-131,162-208,218-227,237-269,273-390,394-443,453-468,485-501
110
- ../core/src/Route.ts | 0.00 | 44.44 | 18-22,28-29,44,55,66,77,88,92,96
111
- ../core/src/Router.ts | 0.00 | 7.68 | 68-121,131-154,182-184,190-193,201-205,212,224-232,243-251,262-270,281-289,300-308,312-354,365-380,414-459,466-471,478-505,512,522-524,531,538-553,557-600,607,614,622,636-644,658-666,680-688,702-710,724-732,743-758,765-803,810-892,899-913
112
- ../core/src/ServiceProvider.ts | 0.00 | 15.07 | 58,91,114-150,179-188,198-209
113
- ../core/src/adapters/GravitoEngineAdapter.ts | 0.00 | 30.43 | 27-31,35-44,48-50,54,58,62-65,69,73,78-81,85-86
114
- ../core/src/adapters/PhotonAdapter.ts | 0.00 | 13.93 | 46,53-55,62-108,112,116,120,124,130-163,167-168,191-195,203-207,214-257,261-262,266-269,273-276,280-283,287-298,302-309,313-318,322,326,330,334,341-355,359,363,367-446,462-467,471-473,487-492,501-512,521-528,564-584,592,596-612,616-626,630-635,639,643-651,656,660-675,683-695,708-710
115
- ../core/src/adapters/bun/BunContext.ts | 0.00 | 12.58 | 38-44,52-75,80-86,90-96,100-106,110-116,120-125,129-134,138,142,146,150-182,189-205,209,214,218-223
116
- ../core/src/adapters/bun/BunNativeAdapter.ts | 0.00 | 10.23 | 21-23,32-36,40-46,50,54,58-87,91,95,99-207
117
- ../core/src/adapters/bun/BunRequest.ts | 0.00 | 10.62 | 13-17,22-34,39,43,47-55,59-62,68-108,114,118-131,135-146
118
- ../core/src/adapters/bun/RadixNode.ts | 0.00 | 17.65 | 39-40,44-52,56-72
119
- ../core/src/adapters/bun/RadixRouter.ts | 0.00 | 8.13 | 17,24-62,69-84,88-148,152-162,169-175,182-193
120
- ../core/src/adapters/bun/types.ts | 100.00 | 100.00 |
121
- ../core/src/adapters/types.ts | 0.00 | 0.00 | 280-287
122
- ../core/src/engine/AOTRouter.ts | 0.00 | 10.78 | 67-85,93-133,144-145,157-159,172-208,215,228-272,279,294-306,320-327,334-345
123
- ../core/src/engine/FastContext.ts | 0.00 | 19.09 | 30,37-45,52-61,65-84,88-89,93-94,98-101,105-109,113-129,133-134,138-169,192-198,208-212,219-223,231-236,240-245,249-254,258-263,267-271,275-280,284,288,292,296-319,329-334,338,349,353,361-363
124
- ../core/src/engine/Gravito.ts | 0.00 | 14.67 | 38-52,82-103,118,125,132,139,146,153,160,167-171,184-197,208-214,225-226,233-255,293-322,330-367,374-391,398-410,417-421,428-456,463-519
125
- ../core/src/engine/MinimalContext.ts | 0.00 | 21.57 | 23-37,41,45,52-64,68,72-84,88,92-112,130,139-142,146-149,153-156,160-163,167-170,174-177,183-187,195-198,202,206,210,214-225,229,235-237
126
- ../core/src/engine/analyzer.ts | 0.00 | 2.08 | 25-49,56-77
127
- ../core/src/engine/constants.ts | 100.00 | 100.00 |
128
- ../core/src/engine/index.ts | 100.00 | 100.00 |
129
- ../core/src/engine/path.ts | 0.00 | 2.63 | 22-43,51-65
130
- ../core/src/engine/pool.ts | 0.00 | 16.67 | 44-46,58-64,76-79,89-103,114-119
131
- ../core/src/exceptions/AuthenticationException.ts | 0.00 | 42.86 | 8-11
132
- ../core/src/exceptions/AuthorizationException.ts | 0.00 | 42.86 | 8-11
133
- ../core/src/exceptions/GravitoException.ts | 0.00 | 19.05 | 24-34,39-44
134
- ../core/src/exceptions/HttpException.ts | 0.00 | 60.00 | 9-10
135
- ../core/src/exceptions/ModelNotFoundException.ts | 0.00 | 25.00 | 11-19
136
- ../core/src/exceptions/ValidationException.ts | 0.00 | 35.71 | 22-26,30-31,35-36
137
- ../core/src/exceptions/index.ts | 100.00 | 100.00 |
138
- ../core/src/helpers.ts | 0.00 | 23.14 | 19,54-59,81-82,107-108,133-139,143-147,176-196,218,222-228,248-253,272-277,306-308,323,335,356-359,381-384,403,417,438-441,461-463,483-489
139
- ../core/src/helpers/Arr.ts | 0.00 | 14.58 | 9-13,17,21,25-28,32-41,45-54,58-64,68-75,79-92,96-109,113-120
140
- ../core/src/helpers/Str.ts | 0.00 | 18.56 | 5-10,14-17,27,31,35-41,45-51,55-61,65-66,70-71,75-77,81-89,93-95,99-105,109-117,121-124,128-134
141
- ../core/src/helpers/data.ts | 0.00 | 2.17 | 13-30,34-47,51-64,68-88,96-113,121-134,142-177
142
- ../core/src/helpers/errors.ts | 0.00 | 7.69 | 26-43,51-53,61-63
143
- ../core/src/helpers/response.ts | 0.00 | 12.50 | 29,37-44,52-56,64-70
144
- ../core/src/http/CookieJar.ts | 0.00 | 12.33 | 33-46,53-74,81,88,95-117,124-126
145
- ../core/src/http/middleware/BodySizeLimit.ts | 0.00 | 2.70 | 20-55
146
- ../core/src/http/middleware/Cors.ts | 0.00 | 1.64 | 23-35,43-89
147
- ../core/src/http/middleware/Csrf.ts | 0.00 | 5.05 | 19-24,28-34,38,42-67,75-91,99-135
148
- ../core/src/http/middleware/HeaderTokenGate.ts | 0.00 | 4.55 | 25-35,45-54
149
- ../core/src/http/middleware/SecurityHeaders.ts | 0.00 | 1.67 | 28,32-35,39-46,54-99
150
- ../core/src/http/middleware/ThrottleRequests.ts | 0.00 | 8.51 | 31-73
151
- ../core/src/index.ts | 0.00 | 96.97 |
152
- ../core/src/runtime.ts | 21.05 | 11.87 | 118-131,138-145,149-166,175,188-190,193-216,221-225,230-325,330-420,425-448,467-470,481-518,526-533
153
- ../core/src/security/Encrypter.ts | 0.00 | 13.70 | 21-35,42-59,66-82,86-88,92-97,102-103,110-111
154
- ../core/src/security/Hasher.ts | 0.00 | 9.09 | 25-43
155
- ../core/src/testing/HttpTester.ts | 0.00 | 4.27 | 11-121
156
- ../core/src/testing/TestResponse.ts | 0.00 | 10.92 | 16-17,24,31,38,45,52,59-124,131-132,139-169
157
- ../core/src/testing/index.ts | 100.00 | 100.00 |
158
- ../core/src/types/events.ts | 0.00 | 14.29 | 91-93,101-104,111-125,132-139
159
- ../enterprise/src/Application/Command.ts | 100.00 | 100.00 |
160
- ../enterprise/src/Application/Query.ts | 100.00 | 100.00 |
161
- ../enterprise/src/Application/UseCase.ts | 100.00 | 100.00 |
162
- ../enterprise/src/Domain/AggregateRoot.ts | 50.00 | 55.56 | 1-8
163
- ../enterprise/src/Domain/DomainEvent.ts | 50.00 | 55.56 | 16-19
164
- ../enterprise/src/Domain/Entity.ts | 100.00 | 100.00 |
165
- ../enterprise/src/Domain/Repository.ts | 100.00 | 100.00 |
166
- ../enterprise/src/Domain/ValueObject.ts | 100.00 | 100.00 |
167
- ../enterprise/src/index.ts | 100.00 | 100.00 |
168
- src/Application/MissionControl.ts | 100.00 | 100.00 |
169
- src/Application/PayloadInjector.ts | 100.00 | 100.00 |
170
- src/Application/PoolManager.ts | 100.00 | 100.00 |
171
- src/Application/RefurbishUnit.ts | 100.00 | 100.00 |
172
- src/Domain/Events.ts | 100.00 | 100.00 |
173
- src/Domain/Mission.ts | 83.33 | 100.00 |
174
- src/Domain/Rocket.ts | 100.00 | 100.00 |
175
- src/Domain/RocketStatus.ts | 100.00 | 100.00 |
176
- src/Infrastructure/Docker/DockerAdapter.ts | 94.44 | 100.00 |
177
- src/Infrastructure/Persistence/InMemoryRocketRepository.ts | 90.91 | 100.00 |
178
- ------------------------------------------------------------|---------|---------|-------------------
179
-
180
- 34 pass
181
- 0 fail
182
- 99 expect() calls
183
- Ran 34 tests across 13 files. [4.34s]