anonymous-id.spec.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
  2. import { tmpdir } from 'node:os'
  3. import { isAbsolute, join, resolve } from 'node:path'
  4. import { defaultDshHome } from '@deepseek-ai/dsh-paths'
  5. import { afterEach, describe, expect, it } from 'vitest'
  6. import {
  7. ANONYMOUS_ID_FILE_NAME,
  8. getOrCreateAnonymousId,
  9. globalConfigDir,
  10. } from '@deepseek-ai/dsh-telemetry'
  11. const dirs: string[] = []
  12. async function tempDir(): Promise<string> {
  13. const dir = await mkdtemp(join(tmpdir(), 'dsh-anon-'))
  14. dirs.push(dir)
  15. return dir
  16. }
  17. afterEach(async () => {
  18. await Promise.all(dirs.splice(0).map(dir => rm(dir, { recursive: true, force: true })))
  19. })
  20. const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
  21. describe('globalConfigDir', () => {
  22. it('prefers an explicit DSH_HOME override', () => {
  23. expect(globalConfigDir({ env: { DSH_HOME: '/custom/dsh' } })).toBe(resolve('/custom/dsh'))
  24. })
  25. it('falls back to ~/.dsh when DSH_HOME is unset', () => {
  26. expect(globalConfigDir({ env: {} })).toBe(resolve(defaultDshHome()))
  27. })
  28. it('reads process.env by default', () => {
  29. // No override supplied: the call must not throw and must return an absolute path.
  30. // The ambient DSH_HOME is unknown here, so assert only the invariant the
  31. // resolver guarantees rather than a specific location.
  32. expect(isAbsolute(globalConfigDir())).toBe(true)
  33. })
  34. })
  35. describe('getOrCreateAnonymousId', () => {
  36. it('creates, persists, and returns a UUID on first use', async () => {
  37. const dir = await tempDir()
  38. const id = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
  39. expect(id).toMatch(UUID)
  40. const stored: unknown = JSON.parse(await readFile(join(dir, ANONYMOUS_ID_FILE_NAME), 'utf8'))
  41. expect(stored).toEqual({ anonymousId: id })
  42. })
  43. it('returns the same persisted id on subsequent calls', async () => {
  44. const dir = await tempDir()
  45. const first = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
  46. const second = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
  47. expect(second).toBe(first)
  48. })
  49. it('uses the injected UUID generator', async () => {
  50. const dir = await tempDir()
  51. const id = await getOrCreateAnonymousId({
  52. env: { DSH_HOME: dir },
  53. randomUUID: () => '00000000-0000-4000-8000-000000000000',
  54. })
  55. expect(id).toBe('00000000-0000-4000-8000-000000000000')
  56. })
  57. it('regenerates when the stored file is corrupt JSON', async () => {
  58. const dir = await tempDir()
  59. await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), 'not json', 'utf8')
  60. const id = await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })
  61. expect(id).toMatch(UUID)
  62. })
  63. it('regenerates when the stored value is not a valid UUID or object', async () => {
  64. const dir = await tempDir()
  65. await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), JSON.stringify({ anonymousId: 'nope' }), 'utf8')
  66. expect(await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })).toMatch(UUID)
  67. await writeFile(join(dir, ANONYMOUS_ID_FILE_NAME), '123', 'utf8')
  68. expect(await getOrCreateAnonymousId({ env: { DSH_HOME: dir } })).toMatch(UUID)
  69. })
  70. it('returns a usable id even when persistence fails', async () => {
  71. const dir = await tempDir()
  72. // A regular file where a directory is expected makes mkdir/writeFile fail.
  73. await writeFile(join(dir, 'blocker'), 'x', 'utf8')
  74. const id = await getOrCreateAnonymousId({ env: { DSH_HOME: join(dir, 'blocker') } })
  75. expect(id).toMatch(UUID)
  76. })
  77. })