parent
6d50221dd5
commit
ae74c0d595
@ -42,7 +42,7 @@ describe('loadGlobalConfig', () => {
|
||||
expect(config.logLevel).toBe('info');
|
||||
expect(config.provider).toBe('claude');
|
||||
expect(config.model).toBeUndefined();
|
||||
expect(config.verbose).toBeUndefined();
|
||||
expect(config.verbose).toBe(false);
|
||||
expect(config.pipeline).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
137
src/__tests__/resolveConfigValue-no-defaultValue.test.ts
Normal file
137
src/__tests__/resolveConfigValue-no-defaultValue.test.ts
Normal file
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Tests for RESOLUTION_REGISTRY defaultValue removal.
|
||||
*
|
||||
* Verifies that piece, verbose, and autoFetch no longer rely on
|
||||
* RESOLUTION_REGISTRY defaultValue but instead use schema defaults
|
||||
* or other guaranteed sources.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import { mkdirSync, rmSync, writeFileSync, existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
const testId = randomUUID();
|
||||
const testDir = join(tmpdir(), `takt-rcv-test-${testId}`);
|
||||
const globalTaktDir = join(testDir, 'global-takt');
|
||||
const globalConfigPath = join(globalTaktDir, 'config.yaml');
|
||||
|
||||
vi.mock('../infra/config/paths.js', async (importOriginal) => {
|
||||
const original = await importOriginal() as Record<string, unknown>;
|
||||
return {
|
||||
...original,
|
||||
getGlobalConfigPath: () => globalConfigPath,
|
||||
getTaktDir: () => globalTaktDir,
|
||||
};
|
||||
});
|
||||
|
||||
const { resolveConfigValue, resolveConfigValueWithSource, invalidateAllResolvedConfigCache } = await import('../infra/config/resolveConfigValue.js');
|
||||
const { invalidateGlobalConfigCache } = await import('../infra/config/global/globalConfig.js');
|
||||
const { getProjectConfigDir } = await import('../infra/config/paths.js');
|
||||
|
||||
describe('RESOLUTION_REGISTRY defaultValue removal', () => {
|
||||
let projectDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
projectDir = join(testDir, `project-${randomUUID()}`);
|
||||
mkdirSync(projectDir, { recursive: true });
|
||||
mkdirSync(globalTaktDir, { recursive: true });
|
||||
writeFileSync(globalConfigPath, 'language: en\n', 'utf-8');
|
||||
invalidateGlobalConfigCache();
|
||||
invalidateAllResolvedConfigCache();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
invalidateGlobalConfigCache();
|
||||
invalidateAllResolvedConfigCache();
|
||||
if (existsSync(testDir)) {
|
||||
rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
describe('piece', () => {
|
||||
it('should resolve piece from project config DEFAULT_PROJECT_CONFIG when not explicitly set', () => {
|
||||
const value = resolveConfigValue(projectDir, 'piece');
|
||||
expect(value).toBe('default');
|
||||
});
|
||||
|
||||
it('should report source as project when piece comes from DEFAULT_PROJECT_CONFIG', () => {
|
||||
const result = resolveConfigValueWithSource(projectDir, 'piece');
|
||||
expect(result.value).toBe('default');
|
||||
expect(result.source).toBe('project');
|
||||
});
|
||||
|
||||
it('should resolve explicit project piece over default', () => {
|
||||
const configDir = getProjectConfigDir(projectDir);
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
writeFileSync(join(configDir, 'config.yaml'), 'piece: custom-piece\n');
|
||||
|
||||
const value = resolveConfigValue(projectDir, 'piece');
|
||||
expect(value).toBe('custom-piece');
|
||||
});
|
||||
|
||||
it('should resolve piece from global config when global has it', () => {
|
||||
writeFileSync(globalConfigPath, 'language: en\npiece: global-piece\n', 'utf-8');
|
||||
invalidateGlobalConfigCache();
|
||||
|
||||
const result = resolveConfigValueWithSource(projectDir, 'piece');
|
||||
expect(result.value).toBe('default');
|
||||
expect(result.source).toBe('project');
|
||||
});
|
||||
});
|
||||
|
||||
describe('verbose', () => {
|
||||
it('should resolve verbose to false via schema default when not set anywhere', () => {
|
||||
const value = resolveConfigValue(projectDir, 'verbose');
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
|
||||
it('should report source as global when verbose comes from schema default', () => {
|
||||
const result = resolveConfigValueWithSource(projectDir, 'verbose');
|
||||
expect(result.value).toBe(false);
|
||||
expect(result.source).toBe('global');
|
||||
});
|
||||
|
||||
it('should resolve verbose from global config when explicitly set', () => {
|
||||
writeFileSync(globalConfigPath, 'language: en\nverbose: true\n', 'utf-8');
|
||||
invalidateGlobalConfigCache();
|
||||
|
||||
const value = resolveConfigValue(projectDir, 'verbose');
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
|
||||
it('should resolve verbose from project config over global', () => {
|
||||
writeFileSync(globalConfigPath, 'language: en\nverbose: false\n', 'utf-8');
|
||||
invalidateGlobalConfigCache();
|
||||
|
||||
const configDir = getProjectConfigDir(projectDir);
|
||||
mkdirSync(configDir, { recursive: true });
|
||||
writeFileSync(join(configDir, 'config.yaml'), 'piece: default\nverbose: true\n');
|
||||
|
||||
const value = resolveConfigValue(projectDir, 'verbose');
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoFetch', () => {
|
||||
it('should resolve autoFetch to false via schema default when not set', () => {
|
||||
const value = resolveConfigValue(projectDir, 'autoFetch');
|
||||
expect(value).toBe(false);
|
||||
});
|
||||
|
||||
it('should report source as global when autoFetch comes from schema default', () => {
|
||||
const result = resolveConfigValueWithSource(projectDir, 'autoFetch');
|
||||
expect(result.value).toBe(false);
|
||||
expect(result.source).toBe('global');
|
||||
});
|
||||
|
||||
it('should resolve autoFetch from global config when explicitly set', () => {
|
||||
writeFileSync(globalConfigPath, 'language: en\nauto_fetch: true\n', 'utf-8');
|
||||
invalidateGlobalConfigCache();
|
||||
|
||||
const value = resolveConfigValue(projectDir, 'autoFetch');
|
||||
expect(value).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -125,13 +125,13 @@ export interface PersistedGlobalConfig {
|
||||
/** Number of movement previews to inject into interactive mode (0 to disable, max 10) */
|
||||
interactivePreviewMovements?: number;
|
||||
/** Verbose output mode */
|
||||
verbose?: boolean;
|
||||
verbose: boolean;
|
||||
/** Number of tasks to run concurrently in takt run (default: 1 = sequential) */
|
||||
concurrency: number;
|
||||
/** Polling interval in ms for picking up new tasks during takt run (default: 500, range: 100-5000) */
|
||||
taskPollIntervalMs: number;
|
||||
/** Opt-in: fetch remote before cloning to keep clones up-to-date (default: false) */
|
||||
autoFetch?: boolean;
|
||||
autoFetch: boolean;
|
||||
/** Base branch to clone from (default: current branch) */
|
||||
baseBranch?: string;
|
||||
}
|
||||
|
||||
@ -489,7 +489,7 @@ export const GlobalConfigSchema = z.object({
|
||||
/** Number of movement previews to inject into interactive mode (0 to disable, max 10) */
|
||||
interactive_preview_movements: z.number().int().min(0).max(10).optional().default(3),
|
||||
/** Verbose output mode */
|
||||
verbose: z.boolean().optional(),
|
||||
verbose: z.boolean().optional().default(false),
|
||||
/** Number of tasks to run concurrently in takt run (default: 1 = sequential, max: 10) */
|
||||
concurrency: z.number().int().min(1).max(10).optional().default(1),
|
||||
/** Polling interval in ms for picking up new tasks during takt run (default: 500, range: 100-5000) */
|
||||
|
||||
@ -356,7 +356,7 @@ export class GlobalConfigManager {
|
||||
if (config.interactivePreviewMovements !== undefined) {
|
||||
raw.interactive_preview_movements = config.interactivePreviewMovements;
|
||||
}
|
||||
if (config.verbose !== undefined) {
|
||||
if (config.verbose) {
|
||||
raw.verbose = config.verbose;
|
||||
}
|
||||
if (config.concurrency !== undefined && config.concurrency > 1) {
|
||||
@ -365,7 +365,7 @@ export class GlobalConfigManager {
|
||||
if (config.taskPollIntervalMs !== undefined && config.taskPollIntervalMs !== 500) {
|
||||
raw.task_poll_interval_ms = config.taskPollIntervalMs;
|
||||
}
|
||||
if (config.autoFetch !== undefined) {
|
||||
if (config.autoFetch) {
|
||||
raw.auto_fetch = config.autoFetch;
|
||||
}
|
||||
if (config.baseBranch) {
|
||||
|
||||
@ -33,7 +33,6 @@ export interface ResolvedConfigValue<K extends ConfigParameterKey> {
|
||||
type ResolutionLayer = 'local' | 'piece' | 'global';
|
||||
interface ResolutionRule<K extends ConfigParameterKey> {
|
||||
layers: readonly ResolutionLayer[];
|
||||
defaultValue?: LoadedConfig[K];
|
||||
mergeMode?: 'analytics';
|
||||
pieceValue?: (pieceContext: PieceContext | undefined) => LoadedConfig[K] | undefined;
|
||||
}
|
||||
@ -61,7 +60,7 @@ const PROVIDER_OPTIONS_ENV_PATHS = [
|
||||
] as const;
|
||||
|
||||
const RESOLUTION_REGISTRY: Partial<{ [K in ConfigParameterKey]: ResolutionRule<K> }> = {
|
||||
piece: { layers: ['local', 'global'], defaultValue: 'default' },
|
||||
piece: { layers: ['local', 'global'] },
|
||||
provider: {
|
||||
layers: ['local', 'piece', 'global'],
|
||||
pieceValue: (pieceContext) => pieceContext?.provider,
|
||||
@ -77,8 +76,8 @@ const RESOLUTION_REGISTRY: Partial<{ [K in ConfigParameterKey]: ResolutionRule<K
|
||||
autoPr: { layers: ['local', 'global'] },
|
||||
draftPr: { layers: ['local', 'global'] },
|
||||
analytics: { layers: ['local', 'global'], mergeMode: 'analytics' },
|
||||
verbose: { layers: ['local', 'global'], defaultValue: false },
|
||||
autoFetch: { layers: ['global'], defaultValue: false },
|
||||
verbose: { layers: ['local', 'global'] },
|
||||
autoFetch: { layers: ['global'] },
|
||||
baseBranch: { layers: ['local', 'global'] },
|
||||
};
|
||||
|
||||
@ -159,7 +158,7 @@ function resolveByRegistry<K extends ConfigParameterKey>(
|
||||
}
|
||||
}
|
||||
|
||||
return { value: rule.defaultValue as LoadedConfig[K], source: 'default' };
|
||||
return { value: undefined as LoadedConfig[K], source: 'default' };
|
||||
}
|
||||
|
||||
function hasProviderOptionsEnvOverride(): boolean {
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import type { PersistedGlobalConfig } from '../../core/models/persisted-global-config.js';
|
||||
|
||||
export interface LoadedConfig extends Omit<PersistedGlobalConfig, 'verbose'> {
|
||||
export interface LoadedConfig extends PersistedGlobalConfig {
|
||||
piece: string;
|
||||
verbose: boolean;
|
||||
}
|
||||
|
||||
export type ConfigParameterKey = keyof LoadedConfig;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user