mirror of
https://github.com/langgenius/dify.git
synced 2026-11-13 14:51:39 +08:00
142 lines
4.8 KiB
TypeScript
142 lines
4.8 KiB
TypeScript
import type { Login } from '@/plugins/session'
|
|
import type { BufferStreams } from '@/sys/io/streams'
|
|
import { createHash } from 'node:crypto'
|
|
import { mkdirSync, mkdtempSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { afterEach, beforeEach, expect, it } from 'vite-plus/test'
|
|
import { Context } from '@/kernel/context'
|
|
import { io, ioService } from '@/plugins/io'
|
|
import { session } from '@/plugins/session'
|
|
import { platform } from '@/sys'
|
|
import { bufferStreams } from '@/sys/io/streams'
|
|
import { catalog, CATALOG_DIR_NAME } from './index'
|
|
|
|
const FIXTURE = readFileSync(join(__dirname, '../../../test/fixtures/catalog.json'))
|
|
const LOGIN: Login = {
|
|
server: 'https://d.example',
|
|
email: 'e',
|
|
account: { id: 'a', email: 'e', name: 'n' },
|
|
workspaceId: null,
|
|
tokenStorage: 'file',
|
|
insecure: false,
|
|
}
|
|
let dir: string
|
|
beforeEach(() => {
|
|
dir = mkdtempSync(join(tmpdir(), 'difyctl-cat-'))
|
|
process.env.DIFY_CONFIG_DIR = dir
|
|
})
|
|
afterEach(() => {
|
|
delete process.env.DIFY_CONFIG_DIR
|
|
delete process.env.DIFY_CACHE_DIR
|
|
})
|
|
|
|
const CACHE_FILE = 'd.example.json'
|
|
|
|
async function build() {
|
|
const ctx = new Context()
|
|
await (await ctx.get(session)).save(LOGIN)
|
|
return ctx.get(catalog)
|
|
}
|
|
|
|
async function buildWithStreams(): Promise<{ streams: BufferStreams; ctx: Context }> {
|
|
const streams = bufferStreams()
|
|
const ctx = new Context([[io, ioService(streams)]])
|
|
await (await ctx.get(session)).save(LOGIN)
|
|
return { streams, ctx }
|
|
}
|
|
|
|
function writeCorruptCache(): string {
|
|
const dir_ = join(dir, CATALOG_DIR_NAME)
|
|
mkdirSync(dir_, { recursive: true })
|
|
const path = join(dir_, CACHE_FILE)
|
|
writeFileSync(path, '{ not json')
|
|
return path
|
|
}
|
|
|
|
it('knows nothing before a catalog is loaded', async () => {
|
|
const c = await build()
|
|
expect(c.loaded).toBe(false)
|
|
expect(c.ops()).toEqual({})
|
|
expect(c.op('get.console_app')).toBeUndefined()
|
|
expect(c.fingerprint).toBe('')
|
|
})
|
|
|
|
it('replace writes the raw bytes; the fingerprint is the sha256 of those bytes', async () => {
|
|
const c = await build()
|
|
await c.replace(FIXTURE)
|
|
expect(c.loaded).toBe(true)
|
|
expect(readFileSync(join(dir, 'catalog', 'd.example.json'))).toEqual(FIXTURE)
|
|
expect(c.fingerprint).toBe(createHash('sha256').update(FIXTURE).digest('hex'))
|
|
const again = await build()
|
|
expect(again.loaded).toBe(true)
|
|
expect(again.op('run.console_app.workflow')?.kind).toBe('sse')
|
|
})
|
|
|
|
it('clear() forgets everything', async () => {
|
|
const c = await build()
|
|
await c.replace(FIXTURE)
|
|
await c.clear()
|
|
expect(c.loaded).toBe(false)
|
|
expect(c.ops()).toEqual({})
|
|
})
|
|
|
|
it('clear() without a login rejects like path and replace do', async () => {
|
|
const c = await new Context().get(catalog)
|
|
await expect(c.clear()).rejects.toMatchObject({ code: 'not_logged_in' })
|
|
})
|
|
|
|
it('a cache file it cannot parse is a miss with one notice, and a refresh still works', async () => {
|
|
const path = writeCorruptCache()
|
|
const { streams, ctx } = await buildWithStreams()
|
|
const c = await ctx.get(catalog)
|
|
expect(c.loaded).toBe(false)
|
|
expect(c.fingerprint).toBe('')
|
|
expect(streams.errBuf().trim().split('\n')).toHaveLength(1)
|
|
expect(streams.errBuf()).toBe(
|
|
`ignoring unreadable catalog cache ${path}: catalog document is not valid JSON\n`,
|
|
)
|
|
|
|
await c.replace(FIXTURE)
|
|
expect(c.loaded).toBe(true)
|
|
expect(readFileSync(path)).toEqual(FIXTURE)
|
|
})
|
|
|
|
it.skipIf(platform() === 'win32')('writes the cache 0600 under a 0700 directory', async () => {
|
|
const c = await build()
|
|
await c.replace(FIXTURE)
|
|
const path = join(dir, CATALOG_DIR_NAME, CACHE_FILE)
|
|
expect(statSync(path).mode & 0o777).toBe(0o600)
|
|
expect(statSync(join(dir, CATALOG_DIR_NAME)).mode & 0o777).toBe(0o700)
|
|
})
|
|
|
|
it('a cache it cannot write is an error naming the path', async () => {
|
|
const c = await build()
|
|
// A file where the catalog directory belongs: mkdir cannot make the parent.
|
|
writeFileSync(join(dir, CATALOG_DIR_NAME), 'not a directory')
|
|
await expect(c.replace(FIXTURE)).rejects.toMatchObject({
|
|
code: 'unknown',
|
|
message: `failed to write the catalog cache ${join(dir, CATALOG_DIR_NAME, CACHE_FILE)}`,
|
|
})
|
|
expect(c.loaded).toBe(false)
|
|
})
|
|
|
|
it('an unknown op id points at the help map, which lists every operation', async () => {
|
|
const c = await build()
|
|
await c.replace(FIXTURE)
|
|
expect(() => c.opOrThrow('get.nothing_here')).toThrow(
|
|
expect.objectContaining({ code: 'unknown_op', hint: 'run difyctl help' }),
|
|
)
|
|
})
|
|
|
|
it('keeps the cache in the config folder and says once that DIFY_CACHE_DIR is no longer used', async () => {
|
|
process.env.DIFY_CACHE_DIR = join(dir, 'old-cache')
|
|
const { streams, ctx } = await buildWithStreams()
|
|
const c = await ctx.get(catalog)
|
|
await ctx.get(catalog)
|
|
expect(c.path).toBe(join(dir, CATALOG_DIR_NAME, CACHE_FILE))
|
|
expect(streams.errBuf()).toBe(
|
|
'DIFY_CACHE_DIR is no longer used; the catalog cache follows DIFY_CONFIG_DIR\n',
|
|
)
|
|
})
|