launch-environment.spec.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
  1. import { describe, expect, it, vi } from 'vitest'
  2. import { Context } from '@deepseek-ai/cordis'
  3. import {
  4. createLaunchEnvironmentSnapshot, DSH_LAUNCH_ENVIRONMENT_KEY, launchedThroughSsh, launchEnvironmentOf,
  5. } from '../src/index.ts'
  6. const layered = createLaunchEnvironmentSnapshot([
  7. { source: 'process', values: { SHARED: 'from-process', ONLY_PROCESS: 'p' } },
  8. { source: 'project-env', path: '/work/.env', values: { SHARED: 'from-project', ONLY_PROJECT: 'j' } },
  9. { source: 'user-env', path: '/home/.dsh/.env', values: { SHARED: 'from-user', ONLY_USER: 'u' } },
  10. ])
  11. describe('launchedThroughSsh', () => {
  12. it.each(['SSH_CONNECTION', 'SSH_TTY'].flatMap(name =>
  13. (['process', 'project-env', 'user-env'] as const).map(source => ({ name, source })),
  14. ))('classifies $name from $source', ({ name, source }) => {
  15. const snapshot = createLaunchEnvironmentSnapshot([{ source, values: { [name]: 'ssh-marker' } }])
  16. expect(launchedThroughSsh(snapshot)).toBe(source === 'process')
  17. })
  18. it.each([{}, { SSH_CONNECTION: '', SSH_TTY: '' }])('keeps absent or empty inherited markers local: %j', (values) => {
  19. const snapshot = createLaunchEnvironmentSnapshot([
  20. { source: 'process', values },
  21. { source: 'project-env', values: { SSH_CONNECTION: 'stale-connection' } },
  22. { source: 'user-env', values: { SSH_TTY: 'stale-tty' } },
  23. ])
  24. expect(launchedThroughSsh(snapshot)).toBe(false)
  25. })
  26. })
  27. describe('createLaunchEnvironmentSnapshot', () => {
  28. it('resolves across every layer, most trusted first, and reports the winning source', () => {
  29. expect(layered.get('SHARED')).toEqual({ value: 'from-process', source: 'process' })
  30. expect(layered.get('ONLY_PROJECT')).toEqual({ value: 'j', source: 'project-env', path: '/work/.env' })
  31. expect(layered.get('ONLY_USER')).toEqual({ value: 'u', source: 'user-env', path: '/home/.dsh/.env' })
  32. expect(layered.get('ABSENT')).toBeUndefined()
  33. })
  34. it('filters layers without changing their trust order', () => {
  35. // The point of getFrom: a routing field that must never come from a
  36. // project directory cannot be reached by reordering, only by listing it.
  37. expect(layered.getFrom('ONLY_PROJECT', ['process', 'user-env'])).toBeUndefined()
  38. expect(layered.getFrom('SHARED', ['user-env', 'process']))
  39. .toEqual({ value: 'from-process', source: 'process' })
  40. expect(layered.getFrom('SHARED', [])).toBeUndefined()
  41. })
  42. it('copies each layer, so a later mutation of the source object cannot change it', () => {
  43. const values: Record<string, string> = { KEY: 'first' }
  44. const snapshot = createLaunchEnvironmentSnapshot([{ source: 'process', values }])
  45. values.KEY = 'second'
  46. values.LATE = 'added'
  47. expect(snapshot.get('KEY')).toEqual({ value: 'first', source: 'process' })
  48. expect(snapshot.get('LATE')).toBeUndefined()
  49. })
  50. it('keeps an empty value as a present value, for its owner to judge', () => {
  51. const snapshot = createLaunchEnvironmentSnapshot([{ source: 'process', values: { EMPTY: '' } }])
  52. expect(snapshot.get('EMPTY')).toEqual({ value: '', source: 'process' })
  53. })
  54. it('orders lookups canonically regardless of construction order', () => {
  55. const reversed = createLaunchEnvironmentSnapshot([
  56. { source: 'user-env', path: '/u', values: { K: 'u' } },
  57. { source: 'process', values: { K: 'p' } },
  58. ])
  59. expect(reversed.get('K')).toEqual({ value: 'p', source: 'process' })
  60. })
  61. })
  62. describe('launchEnvironmentOf', () => {
  63. it('returns the launcher snapshot when the product CLI provided one', () => {
  64. const ctx = new Context()
  65. ctx.provide(DSH_LAUNCH_ENVIRONMENT_KEY, layered)
  66. expect(launchEnvironmentOf(ctx)).toBe(layered)
  67. })
  68. it('falls back to the inherited environment as the only layer', () => {
  69. vi.stubEnv('DSH_ENV_SPEC_FALLBACK', 'ambient')
  70. try {
  71. const snapshot = launchEnvironmentOf(new Context())
  72. expect(snapshot.get('DSH_ENV_SPEC_FALLBACK')).toEqual({ value: 'ambient', source: 'process' })
  73. } finally {
  74. vi.unstubAllEnvs()
  75. }
  76. })
  77. })