mirror of
https://github.com/langgenius/dify.git
synced 2026-05-30 13:47:52 +08:00
227 lines
7.4 KiB
TypeScript
227 lines
7.4 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { UnsupportedArgValueError } from './errors.js'
|
|
import { Args, Flags, parseArgv } from './flags.js'
|
|
|
|
const meta = {
|
|
flags: {
|
|
output: Flags.string({ description: 'output format', char: 'o' }),
|
|
verbose: Flags.boolean({ description: 'verbose mode', char: 'v' }),
|
|
count: Flags.integer({ description: 'count', default: 5 }),
|
|
format: Flags.string({ description: 'format', default: 'text' }),
|
|
},
|
|
args: {
|
|
name: Args.string({ description: 'name', required: true }),
|
|
extra: Args.string({ description: 'extra' }),
|
|
},
|
|
}
|
|
|
|
describe('parseArgv', () => {
|
|
describe('positional args', () => {
|
|
it('parses required arg', () => {
|
|
const { args } = parseArgv(['alice'], meta)
|
|
expect(args.name).toBe('alice')
|
|
})
|
|
|
|
it('parses optional arg when provided', () => {
|
|
const { args } = parseArgv(['alice', 'bonus'], meta)
|
|
expect(args.name).toBe('alice')
|
|
expect(args.extra).toBe('bonus')
|
|
})
|
|
|
|
it('leaves optional arg undefined when absent', () => {
|
|
const { args } = parseArgv(['alice'], meta)
|
|
expect(args.extra).toBeUndefined()
|
|
})
|
|
|
|
it('throws on missing required arg', () => {
|
|
expect(() => parseArgv([], meta)).toThrow('missing required argument: name')
|
|
})
|
|
})
|
|
|
|
describe('long flags (--flag value)', () => {
|
|
it('parses string flag with space separator', () => {
|
|
const { flags } = parseArgv(['alice', '--output', 'json'], meta)
|
|
expect(flags.output).toBe('json')
|
|
})
|
|
|
|
it('parses string flag with = separator', () => {
|
|
const { flags } = parseArgv(['alice', '--output=yaml'], meta)
|
|
expect(flags.output).toBe('yaml')
|
|
})
|
|
|
|
it('parses boolean flag as true when bare', () => {
|
|
const { flags } = parseArgv(['alice', '--verbose'], meta)
|
|
expect(flags.verbose).toBe(true)
|
|
})
|
|
|
|
it('parses boolean flag with =true', () => {
|
|
const { flags } = parseArgv(['alice', '--verbose=true'], meta)
|
|
expect(flags.verbose).toBe(true)
|
|
})
|
|
|
|
it('parses boolean flag with =false', () => {
|
|
const { flags } = parseArgv(['alice', '--verbose=false'], meta)
|
|
expect(flags.verbose).toBe(false)
|
|
})
|
|
|
|
it('parses integer flag', () => {
|
|
const { flags } = parseArgv(['alice', '--count', '10'], meta)
|
|
expect(flags.count).toBe(10)
|
|
})
|
|
|
|
it('parses integer flag with = separator', () => {
|
|
const { flags } = parseArgv(['alice', '--count=42'], meta)
|
|
expect(flags.count).toBe(42)
|
|
})
|
|
|
|
it('throws on non-integer value for integer flag', () => {
|
|
expect(() => parseArgv(['alice', '--count', 'abc'], meta)).toThrow('expected integer, got "abc"')
|
|
})
|
|
|
|
it('throws on invalid boolean value', () => {
|
|
expect(() => parseArgv(['alice', '--verbose=maybe'], meta)).toThrow('expected boolean, got "maybe"')
|
|
})
|
|
|
|
it('throws when non-boolean flag has no value', () => {
|
|
expect(() => parseArgv(['alice', '--output'], meta)).toThrow('flag --output expects a value')
|
|
})
|
|
|
|
it('throws on unknown long flag', () => {
|
|
expect(() => parseArgv(['alice', '--unknown', 'x'], meta)).toThrow('unknown flag: --unknown')
|
|
})
|
|
})
|
|
|
|
describe('short flags (-x)', () => {
|
|
it('parses short boolean flag', () => {
|
|
const { flags } = parseArgv(['alice', '-v'], meta)
|
|
expect(flags.verbose).toBe(true)
|
|
})
|
|
|
|
it('parses short string flag with space-separated value', () => {
|
|
const { flags } = parseArgv(['alice', '-o', 'json'], meta)
|
|
expect(flags.output).toBe('json')
|
|
})
|
|
|
|
it('throws when short non-boolean flag has no value', () => {
|
|
expect(() => parseArgv(['alice', '-o'], meta)).toThrow('flag -o expects a value')
|
|
})
|
|
|
|
it('throws on unknown short flag', () => {
|
|
expect(() => parseArgv(['alice', '-z'], meta)).toThrow('unknown flag: -z')
|
|
})
|
|
})
|
|
|
|
describe('multiple: true', () => {
|
|
const multipleMeta = {
|
|
flags: {
|
|
label: Flags.stringArray({ description: 'labels' }),
|
|
output: Flags.string({ description: 'output', char: 'o' }),
|
|
},
|
|
args: {},
|
|
}
|
|
|
|
it('collects repeated long flags into an array', () => {
|
|
const { flags } = parseArgv(['--label', 'foo', '--label', 'bar'], multipleMeta)
|
|
expect(flags.label).toEqual(['foo', 'bar'])
|
|
})
|
|
|
|
it('collects repeated long flags with = separator', () => {
|
|
const { flags } = parseArgv(['--label=foo', '--label=bar', '--label=baz'], multipleMeta)
|
|
expect(flags.label).toEqual(['foo', 'bar', 'baz'])
|
|
})
|
|
|
|
it('collects repeated short flags into an array', () => {
|
|
const multipleShortMeta = {
|
|
flags: { label: Flags.string({ description: 'labels', multiple: true, char: 'l' }) },
|
|
args: {},
|
|
}
|
|
const { flags } = parseArgv(['-l', 'foo', '-l', 'bar'], multipleShortMeta)
|
|
expect(flags.label).toEqual(['foo', 'bar'])
|
|
})
|
|
|
|
it('single occurrence still produces array with one element', () => {
|
|
const { flags } = parseArgv(['--label', 'only'], multipleMeta)
|
|
expect(flags.label).toEqual(['only'])
|
|
})
|
|
|
|
it('absent multiple flag is undefined', () => {
|
|
const { flags } = parseArgv([], multipleMeta)
|
|
expect(flags.label).toBeUndefined()
|
|
})
|
|
|
|
it('non-multiple flag is not affected', () => {
|
|
const { flags } = parseArgv(['--output', 'json'], multipleMeta)
|
|
expect(flags.output).toBe('json')
|
|
})
|
|
})
|
|
|
|
describe('double-dash (--) separator', () => {
|
|
it('treats tokens after -- as positional args', () => {
|
|
const { args, flags } = parseArgv(['alice', '--', '--output', 'json'], meta)
|
|
expect(flags.output).toBeUndefined()
|
|
expect(args.extra).toBe('--output')
|
|
})
|
|
})
|
|
|
|
describe('defaults', () => {
|
|
it('applies flag default when flag is absent', () => {
|
|
const { flags } = parseArgv(['alice'], meta)
|
|
expect(flags.count).toBe(5)
|
|
expect(flags.format).toBe('text')
|
|
})
|
|
|
|
it('does not apply default when flag is provided', () => {
|
|
const { flags } = parseArgv(['alice', '--count', '99'], meta)
|
|
expect(flags.count).toBe(99)
|
|
})
|
|
})
|
|
|
|
describe('options validation', () => {
|
|
const metaWithOptions = {
|
|
flags: {
|
|
mode: Flags.string({ description: 'app mode', options: ['chat', 'workflow', 'completion'] }),
|
|
},
|
|
args: {},
|
|
}
|
|
|
|
it('accepts a valid option value', () => {
|
|
const { flags } = parseArgv(['--mode', 'chat'], metaWithOptions)
|
|
expect(flags.mode).toBe('chat')
|
|
})
|
|
|
|
it('rejects an invalid option value (space form)', () => {
|
|
expect(() => parseArgv(['--mode', 'chatbot'], metaWithOptions)).toThrow(
|
|
UnsupportedArgValueError,
|
|
)
|
|
})
|
|
|
|
it('rejects an invalid option value (= form)', () => {
|
|
expect(() => parseArgv(['--mode=chatbot'], metaWithOptions)).toThrow(
|
|
UnsupportedArgValueError,
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('Flags and Args factory', () => {
|
|
it('Flags.string produces string type definition', () => {
|
|
const def = Flags.string({ description: 'test' })
|
|
expect(def.type).toBe('string')
|
|
})
|
|
|
|
it('Flags.boolean produces boolean type definition', () => {
|
|
const def = Flags.boolean({ description: 'test' })
|
|
expect(def.type).toBe('boolean')
|
|
})
|
|
|
|
it('Flags.integer produces integer type definition', () => {
|
|
const def = Flags.integer({ description: 'test' })
|
|
expect(def.type).toBe('integer')
|
|
})
|
|
|
|
it('Args.string produces an arg definition with required when set', () => {
|
|
const def = Args.string({ description: 'test', required: true })
|
|
expect(def.required).toBe(true)
|
|
})
|
|
})
|
|
})
|