mirror of
https://github.com/langgenius/dify.git
synced 2026-06-16 22:11:09 +08:00
82 lines
3.3 KiB
TypeScript
82 lines
3.3 KiB
TypeScript
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
|
import { FileTokenStore } from './token-store'
|
|
|
|
describe('FileTokenStore', () => {
|
|
let dir: string
|
|
let file: string
|
|
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'difyctl-tok-'))
|
|
file = join(dir, 'tokens.yml')
|
|
})
|
|
afterEach(() => rmSync(dir, { recursive: true, force: true }))
|
|
|
|
it('returns empty string for a missing credential', async () => {
|
|
const s = new FileTokenStore(file)
|
|
expect(await s.read('https://cloud.dify.ai', 'a@x.com')).toBe('')
|
|
})
|
|
|
|
it('round-trips a bearer with dots and @ kept literal', async () => {
|
|
const s = new FileTokenStore(file)
|
|
await s.write('https://cloud.dify.ai', 'a.b@x.com', 'dfoa_secret')
|
|
expect(await s.read('https://cloud.dify.ai', 'a.b@x.com')).toBe('dfoa_secret')
|
|
})
|
|
|
|
it('keeps multiple accounts under one host and isolates hosts', async () => {
|
|
const s = new FileTokenStore(file)
|
|
await s.write('https://cloud.dify.ai', 'a@x.com', 'A')
|
|
await s.write('https://cloud.dify.ai', 'b@x.com', 'B')
|
|
await s.write('https://self.example.com', 'a@x.com', 'C')
|
|
expect(await s.read('https://cloud.dify.ai', 'a@x.com')).toBe('A')
|
|
expect(await s.read('https://cloud.dify.ai', 'b@x.com')).toBe('B')
|
|
expect(await s.read('https://self.example.com', 'a@x.com')).toBe('C')
|
|
})
|
|
|
|
it('persists the versioned nested shape on disk', async () => {
|
|
const s = new FileTokenStore(file)
|
|
await s.write('https://cloud.dify.ai', 'a@x.com', 'A')
|
|
const raw = readFileSync(file, 'utf8')
|
|
expect(raw).toContain('version: 1')
|
|
expect(raw).toContain('https://cloud.dify.ai')
|
|
expect(raw).toContain('a@x.com')
|
|
})
|
|
|
|
it('reads empty when the document version is an unknown future version', async () => {
|
|
writeFileSync(file, 'version: 999\ntokens:\n "h":\n "e": "x"\n')
|
|
const s = new FileTokenStore(file)
|
|
expect(await s.read('h', 'e')).toBe('')
|
|
})
|
|
|
|
it('reads tokens from legacy format (no version field) for transparent migration', async () => {
|
|
writeFileSync(file, 'tokens:\n "h":\n "e": "dfoa_legacy"\n')
|
|
const s = new FileTokenStore(file)
|
|
expect(await s.read('h', 'e')).toBe('dfoa_legacy')
|
|
})
|
|
|
|
it('preserves existing tokens and stamps version when writing to a legacy file', async () => {
|
|
writeFileSync(file, 'tokens:\n "h":\n "existing@x": "dfoa_existing"\n')
|
|
const s = new FileTokenStore(file)
|
|
await s.write('h', 'new@x', 'dfoa_new')
|
|
expect(await s.read('h', 'existing@x')).toBe('dfoa_existing')
|
|
expect(await s.read('h', 'new@x')).toBe('dfoa_new')
|
|
expect(readFileSync(file, 'utf8')).toContain('version: 1')
|
|
})
|
|
|
|
it('remove deletes the credential and prunes the empty host map', async () => {
|
|
const s = new FileTokenStore(file)
|
|
await s.write('https://cloud.dify.ai', 'a@x.com', 'A')
|
|
await s.remove('https://cloud.dify.ai', 'a@x.com')
|
|
expect(await s.read('https://cloud.dify.ai', 'a@x.com')).toBe('')
|
|
const raw = readFileSync(file, 'utf8')
|
|
expect(raw).not.toContain('cloud.dify.ai')
|
|
})
|
|
|
|
it('remove is a no-op for an absent credential', async () => {
|
|
const s = new FileTokenStore(file)
|
|
await expect(s.remove('h', 'e')).resolves.not.toThrow()
|
|
})
|
|
})
|