소스 검색

test: cover gateway rollback and theme fallback branches

imccyu 3 주 전
부모
커밋
3ca4997cd2
2개의 변경된 파일79개의 추가작업 그리고 0개의 파일을 삭제
  1. 70 0
      packages/api/gateway/tests/gateway.client.spec.ts
  2. 9 0
      packages/client/ui-theme/tests/host.client.spec.ts

+ 70 - 0
packages/api/gateway/tests/gateway.client.spec.ts

@@ -458,6 +458,76 @@ describe('Client Typert API', () => {
     await disposeContext()
   })
 
+  it('unwinds an already-installed namespace when a later namespace fails to install', async () => {
+    const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
+    const { scope: _scope, ...probe } = directDescriptor()
+    const vault: InvocationDescriptor = {
+      ...probe,
+      id: '@fixture/vault#vault/seal',
+      service: 'vault',
+      namespace: 'vault',
+      method: 'seal',
+    }
+    const defineProperty = Object.defineProperty
+    const spy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => {
+      if (key === 'seal') throw new Error('fixture later-namespace failure')
+      return defineProperty(target, key, attributes)
+    })
+    try {
+      await expect(ctx.remote.$mount({ package: '@fixture/two-namespaces', descriptors: [probe, vault] }))
+        .rejects.toThrow('fixture later-namespace failure')
+    } finally {
+      spy.mockRestore()
+    }
+
+    expect((ctx.remote as unknown as Record<string, unknown>).probe).toBeUndefined()
+    expect((ctx.remote as unknown as Record<string, unknown>).vault).toBeUndefined()
+    await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
+  })
+
+  it('rolls back an earlier scoped projection when a later descriptor fails to install', async () => {
+    const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
+    const { scope: _scope, ...direct } = directDescriptor()
+    const failing: InvocationDescriptor = {
+      ...direct,
+      id: '@fixture/probe#probe/archive',
+      method: 'archive',
+    }
+    const defineProperty = Object.defineProperty
+    const spy = vi.spyOn(Object, 'defineProperty').mockImplementation((target, key, attributes) => {
+      if (key === 'archive') throw new Error('fixture trailing failure')
+      return defineProperty(target, key, attributes)
+    })
+    try {
+      await expect(ctx.remote.$mount({
+        package: '@fixture/scoped-then-failing',
+        descriptors: [contextDescriptor(), failing],
+      })).rejects.toThrow('fixture trailing failure')
+    } finally {
+      spy.mockRestore()
+    }
+
+    expect((ctx.remote as unknown as Record<string, unknown>).probe).toBeUndefined()
+    await vi.waitFor(() => { expect(ctx.typert.remotes.list()).toEqual([]) })
+  })
+
+  it('keeps a namespace another contribution still populates when a group leaves', async () => {
+    const call = vi.fn<ConnectionHandle['rpc']['call']>()
+      .mockResolvedValue({ ok: true, value: { renamed: true } })
+    const ctx = await bench(call)
+    const { scope: _scope, ...direct } = directDescriptor()
+    const disposeDirect = await ctx.remote.$mount({ package: '@fixture/direct-owner', descriptors: [direct] })
+    const disposeScoped = await ctx.remote.$mount({ package: '@fixture/scoped-owner', descriptors: [contextDescriptor()] })
+
+    await disposeDirect()
+    // The namespace survives its first group: the second contribution still owns methods on it.
+    const surviving = ctx.get('remote.probe') as unknown as Record<string, unknown> | undefined
+    expect(surviving).toBeDefined()
+    expect(surviving?.create).toBeUndefined()
+    await disposeScoped()
+    expect(ctx.get('remote.probe')).toBeUndefined()
+  })
+
   it('rejects weak parameter and Context codecs plus malformed scope projections', async () => {
     const ctx = await bench(vi.fn<ConnectionHandle['rpc']['call']>())
     const direct = directDescriptor()

+ 9 - 0
packages/client/ui-theme/tests/host.client.spec.ts

@@ -62,4 +62,13 @@ describe('ui-theme host', () => {
     await ctx.plugin({ apply }).await()
     expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"')
   })
+
+  it('falls back to the schema default while the theme namespace holds no section', async () => {
+    // A settings provider whose namespace read comes back empty (registration
+    // still pending or a provider without schema defaults).
+    const ctx = new Context()
+    ctx.provide('settings', { register: () => () => {}, get: () => undefined } as never)
+    await ctx.plugin({ apply }).await()
+    expect(scriptText(collect(ctx)[0])).toContain('const preference = "system"')
+  })
 })