Browse Source

test: update tests for legacy config key removal

Removes assertions for removed tmux and council.master config keys,
and updates mocks/configs that referenced them. The env.test.ts
changes are formatting from biome auto-fix on the updated test files.
Michael Henke 2 weeks ago
parent
commit
dda2ca471a

+ 2 - 2
docs/configuration.md

@@ -227,8 +227,8 @@ subprocess.
   `presets.<name>.council.model`.
 - The **councillor models** are configured separately under
   `council.presets.<name>.<councillor>.model`.
-- Deprecated `council.master*` fields are legacy compatibility aliases only;
-  do not use them in new configs.
+- `council.master*` fields have been removed. A deprecation warning is
+  logged this release if a config still contains them.
 
 ### Manual Update Mode
 

+ 4 - 7
docs/council.md

@@ -348,14 +348,11 @@ A footer tracks participation:
 
 ## Compatibility Notes
 
-### Deprecated `master` fields
+### Removed `master` fields
 
-Older configs used `council.master` and several other `master`-prefixed
-fields. These fields are deprecated and ignored.
-
-`master.model` is still accepted as a temporary fallback for the **Council
-agent model only** when no explicit `council` agent model is configured
-elsewhere.
+The `council.master` field and other `master`-prefixed fields have been
+removed. A deprecation warning is logged this release if a config still
+contains them, but they no longer have any effect.
 
 Prefer this instead:
 

+ 1 - 2
src/agents/council-agents.test.ts

@@ -5,7 +5,7 @@ import { buildCouncillorAgents } from './council-agents';
 /**
  * Build a minimal CouncilConfig for use in tests.
  * We cast through `unknown` to avoid repeating the full post-transform shape
- * which includes `_deprecated` / `_legacyMasterModel` and optional fields.
+ * which includes `_deprecated` and optional fields.
  */
 function makeConfig(overrides: Record<string, unknown>): PluginConfig {
   return {
@@ -13,7 +13,6 @@ function makeConfig(overrides: Record<string, unknown>): PluginConfig {
       presets: {},
       default_preset: 'default',
       _deprecated: undefined,
-      _legacyMasterModel: undefined,
       ...overrides,
     },
   } as unknown as PluginConfig;

+ 6 - 41
src/agents/index.test.ts

@@ -536,41 +536,7 @@ describe('council agent model resolution', () => {
     expect(councillor?.config.model).toBe(DEFAULT_MODELS.councillor);
   });
 
-  test('council falls back to legacy master.model when no preset override', () => {
-    // Simulates a pre-1.0.0 config with council.master.model but no council
-    // entry in the agent preset - the exact scenario from issue #369.
-    const config: PluginConfig = {
-      agents: {
-        oracle: { model: 'openai/gpt-5.6' },
-      },
-      council: {
-        ...councilConfig(),
-        _legacyMasterModel: 'anthropic/claude-opus-4-6',
-      },
-    };
-    const agents = createAgents(config);
-    const council = agents.find((a) => a.name === 'council');
-    expect(council?.config.model).toBe('anthropic/claude-opus-4-6');
-  });
-
-  test('council preset override takes precedence over legacy master.model', () => {
-    // If user has explicit council in preset, that wins - legacy is ignored.
-    const config: PluginConfig = {
-      agents: {
-        council: { model: 'google/gemini-3-pro' },
-      },
-      council: {
-        ...councilConfig(),
-        _legacyMasterModel: 'anthropic/claude-opus-4-6',
-      },
-    };
-    const agents = createAgents(config);
-    const council = agents.find((a) => a.name === 'council');
-    expect(council?.config.model).toBe('google/gemini-3-pro');
-  });
-
-  test('council uses default when no legacy master and no preset override', () => {
-    // No legacy master, no preset override → standard default
+  test('council uses default when no preset override', () => {
     const config: PluginConfig = {
       council: councilConfig(),
     };
@@ -579,10 +545,8 @@ describe('council agent model resolution', () => {
     expect(council?.config.model).toBe(DEFAULT_MODELS.council);
   });
 
-  test('end-to-end: raw master.model config flows through schema to council agent', () => {
-    // Integration test: start from raw user config with deprecated master.model,
-    // parse through CouncilConfigSchema, then pass to createAgents.
-    // This validates the full seam between schema transform and agent resolution.
+  test('deprecated council.master field is ignored', () => {
+    // Verify that the deprecated master field is reported but not applied.
     const rawCouncilConfig = {
       master: { model: 'anthropic/claude-opus-4-6' },
       presets: {
@@ -596,13 +560,14 @@ describe('council agent model resolution', () => {
     expect(parsed.success).toBe(true);
 
     if (parsed.success) {
+      expect(parsed.data._deprecated).toEqual(['master']);
       const config: PluginConfig = {
         council: parsed.data,
       };
       const agents = createAgents(config);
       const council = agents.find((a) => a.name === 'council');
-      // Legacy master.model should flow through schema → agent
-      expect(council?.config.model).toBe('anthropic/claude-opus-4-6');
+      // Master is deprecated and no longer used for model fallback
+      expect(council?.config.model).toBe(DEFAULT_MODELS.council);
     }
   });
 });

+ 0 - 2
src/cli/background-subagents.test.ts

@@ -187,7 +187,6 @@ describe('configureBackgroundSubagents', () => {
 
     try {
       const result = await configureBackgroundSubagents({
-        hasTmux: false,
         installCustomSkills: false,
         promptForStar: false,
         reset: false,
@@ -221,7 +220,6 @@ describe('configureBackgroundSubagents', () => {
 
     try {
       const result = await configureBackgroundSubagents({
-        hasTmux: false,
         installCustomSkills: false,
         promptForStar: false,
         reset: false,

+ 0 - 5
src/cli/config-io.test.ts

@@ -424,7 +424,6 @@ describe('config-io', () => {
     paths.ensureConfigDir();
 
     const result = writeLiteConfig({
-      hasTmux: true,
       installCustomSkills: false,
       reset: false,
     });
@@ -437,7 +436,6 @@ describe('config-io', () => {
     expect(saved.preset).toBe('openai');
     expect(saved.presets.openai).toBeDefined();
     expect(saved.presets['opencode-go']).toBeDefined();
-    expect(saved.tmux.enabled).toBe(true);
   });
 
   test('writeLiteConfig writes selected preset', () => {
@@ -445,7 +443,6 @@ describe('config-io', () => {
     paths.ensureConfigDir();
 
     const result = writeLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       preset: 'opencode-go',
       reset: false,
@@ -568,7 +565,6 @@ describe('config-io', () => {
             librarian: { model: 'zai-coding-plan/glm-4.7' },
           },
         },
-        tmux: { enabled: true },
       }),
     );
 
@@ -579,7 +575,6 @@ describe('config-io', () => {
     expect(detected.hasAnthropic).toBe(true);
     expect(detected.hasCopilot).toBe(true);
     expect(detected.hasZaiPlan).toBe(true);
-    expect(detected.hasTmux).toBe(true);
   });
 
   test('detectCurrentConfig detects provider models in arrays', () => {

+ 0 - 1
src/cli/install.test.ts

@@ -147,7 +147,6 @@ mock.module('./paths', () => {
 
 function baseConfig(): InstallConfig {
   return {
-    hasTmux: false,
     installCustomSkills: false,
     forceSkillSync: false,
     reset: false,

+ 0 - 25
src/cli/providers.test.ts

@@ -17,7 +17,6 @@ describe('providers', () => {
 
   test('generateLiteConfig defaults to openai and includes generated presets', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -42,7 +41,6 @@ describe('providers', () => {
 
   test('generateLiteConfig uses correct OpenAI models', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -64,7 +62,6 @@ describe('providers', () => {
 
   test('generateLiteConfig can set opencode-go as active preset', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       preset: 'opencode-go',
       backgroundSubagents: 'no',
@@ -95,7 +92,6 @@ describe('providers', () => {
   test('generateLiteConfig rejects unsupported preset', () => {
     expect(() =>
       generateLiteConfig({
-        hasTmux: false,
         installCustomSkills: false,
         preset: 'not-real',
         backgroundSubagents: 'no',
@@ -107,7 +103,6 @@ describe('providers', () => {
   test('generateLiteConfig rejects non-generated model mappings as active presets', () => {
     expect(() =>
       generateLiteConfig({
-        hasTmux: false,
         installCustomSkills: false,
         preset: 'kimi',
         backgroundSubagents: 'no',
@@ -119,7 +114,6 @@ describe('providers', () => {
   test('generateLiteConfig rejects inherited property names as presets', () => {
     expect(() =>
       generateLiteConfig({
-        hasTmux: false,
         installCustomSkills: false,
         preset: 'toString',
         backgroundSubagents: 'no',
@@ -128,22 +122,8 @@ describe('providers', () => {
     ).toThrow('Unsupported preset "toString"');
   });
 
-  test('generateLiteConfig enables tmux when requested', () => {
-    const config = generateLiteConfig({
-      hasTmux: true,
-      installCustomSkills: false,
-      backgroundSubagents: 'no',
-      reset: false,
-    });
-
-    expect(config.tmux).toBeDefined();
-    expect((config.tmux as any).enabled).toBe(true);
-    expect((config.tmux as any).layout).toBe('main-vertical');
-  });
-
   test('generateLiteConfig companion: yes', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -158,7 +138,6 @@ describe('providers', () => {
 
   test('generateLiteConfig companion: no or omitted', () => {
     const configYes = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -167,7 +146,6 @@ describe('providers', () => {
     expect(configYes.companion).toBeUndefined();
 
     const configOmitted = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -177,7 +155,6 @@ describe('providers', () => {
 
   test('generateLiteConfig includes default skills', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -205,7 +182,6 @@ describe('providers', () => {
 
   test('generateLiteConfig includes mcps field', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,
@@ -220,7 +196,6 @@ describe('providers', () => {
 
   test('generateLiteConfig openai includes correct mcps', () => {
     const config = generateLiteConfig({
-      hasTmux: false,
       installCustomSkills: false,
       backgroundSubagents: 'no',
       reset: false,

+ 0 - 41
src/config/council-schema.test.ts

@@ -60,8 +60,6 @@ describe('CouncillorConfigSchema', () => {
       // Deprecated fields are stripped but reported via _deprecated
       expect(result.data._deprecated).toEqual(['master']);
       expect(Object.keys(result.data.presets.default)).toEqual(['alpha']);
-      // Legacy master.model is extracted for backward-compat fallback
-      expect(result.data._legacyMasterModel).toBe('anthropic/claude-opus-4-6');
     }
   });
 
@@ -79,7 +77,6 @@ describe('CouncillorConfigSchema', () => {
 
     if (result.success) {
       expect(result.data._deprecated).toBeUndefined();
-      expect(result.data._legacyMasterModel).toBeUndefined();
     }
   });
 });
@@ -163,44 +160,6 @@ test('deprecated master with non-standard model ID still parses', () => {
 
   if (result.success) {
     expect(result.data._deprecated).toEqual(['master']);
-    // Even non-standard model IDs are extracted as-is for backward compat
-    expect(result.data._legacyMasterModel).toBe('claude-opus-4-6');
-  }
-});
-
-test('legacyMasterModel undefined when master.model is not a string', () => {
-  const config = {
-    master: { model: 42 }, // not a string
-    presets: {
-      default: {
-        alpha: { model: 'openai/gpt-5.6-luna' },
-      },
-    },
-  };
-
-  const result = CouncilConfigSchema.safeParse(config);
-  expect(result.success).toBe(true);
-
-  if (result.success) {
-    expect(result.data._legacyMasterModel).toBeUndefined();
-  }
-});
-
-test('legacyMasterModel undefined when master is not an object', () => {
-  const config = {
-    master: 'oops', // not an object
-    presets: {
-      default: {
-        alpha: { model: 'openai/gpt-5.6-luna' },
-      },
-    },
-  };
-
-  const result = CouncilConfigSchema.safeParse(config);
-  expect(result.success).toBe(true);
-
-  if (result.success) {
-    expect(result.data._legacyMasterModel).toBeUndefined();
   }
 });
 

+ 88 - 69
src/config/loader.test.ts

@@ -438,6 +438,94 @@ describe('onWarning callback', () => {
     expect(config.agents?.oracle?.model).toBe('valid/model');
   });
 
+  test('deprecated tmux key calls onWarning with invalid-schema and still loads', () => {
+    const projectDir = path.join(tempDir, 'project');
+    const projectConfigDir = path.join(projectDir, '.opencode');
+    fs.mkdirSync(projectConfigDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
+      JSON.stringify({
+        tmux: { enabled: true, layout: 'main-vertical' },
+        agents: { oracle: { model: 'valid/model' } },
+      }),
+    );
+
+    const warnings: ConfigLoadWarning[] = [];
+    const config = loadPluginConfig(projectDir, {
+      onWarning: (warning) => warnings.push(warning),
+    });
+
+    expect(warnings).toHaveLength(1);
+    expect(warnings[0]?.kind).toBe('invalid-schema');
+    expect(warnings[0]?.message).toContain('Deprecated tmux config key');
+    expect(config.agents?.oracle?.model).toBe('valid/model');
+  });
+
+  test('deprecated council.master key calls onWarning with invalid-schema and still loads', () => {
+    const projectDir = path.join(tempDir, 'project');
+    const projectConfigDir = path.join(projectDir, '.opencode');
+    fs.mkdirSync(projectConfigDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
+      JSON.stringify({
+        council: {
+          master: { model: 'openai/gpt-5.6' },
+          presets: {
+            default: {
+              alpha: { model: 'openai/gpt-5.6-luna' },
+            },
+          },
+        },
+      }),
+    );
+
+    const warnings: ConfigLoadWarning[] = [];
+    const config = loadPluginConfig(projectDir, {
+      onWarning: (warning) => warnings.push(warning),
+    });
+
+    expect(warnings).toHaveLength(1);
+    expect(warnings[0]?.kind).toBe('invalid-schema');
+    expect(warnings[0]?.message).toContain(
+      'Deprecated council.master config key',
+    );
+    expect(config.council?.presets?.default?.alpha?.model).toBe(
+      'openai/gpt-5.6-luna',
+    );
+  });
+
+  test('both deprecated keys fire two warnings', () => {
+    const projectDir = path.join(tempDir, 'project');
+    const projectConfigDir = path.join(projectDir, '.opencode');
+    fs.mkdirSync(projectConfigDir, { recursive: true });
+    fs.writeFileSync(
+      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
+      JSON.stringify({
+        tmux: { enabled: true },
+        council: {
+          master: { model: 'openai/gpt-5.6' },
+          presets: {
+            default: {
+              alpha: { model: 'openai/gpt-5.6-luna' },
+            },
+          },
+        },
+      }),
+    );
+
+    const warnings: ConfigLoadWarning[] = [];
+    loadPluginConfig(projectDir, {
+      onWarning: (warning) => warnings.push(warning),
+    });
+
+    expect(warnings).toHaveLength(2);
+    const messages = warnings.map((w) => w.message);
+    expect(messages.some((m) => m.includes('Deprecated tmux'))).toBe(true);
+    expect(messages.some((m) => m.includes('Deprecated council.master'))).toBe(
+      true,
+    );
+  });
+
   test('no options object does not break loadPluginConfig', () => {
     const projectDir = path.join(tempDir, 'project');
     const projectConfigDir = path.join(projectDir, '.opencode');
@@ -513,69 +601,6 @@ describe('deepMerge behavior', () => {
     expect(config.agents?.designer?.model).toBe('project/designer-model');
   });
 
-  test('merges nested tmux configs', () => {
-    const userOpencodeDir = path.join(userConfigDir, 'opencode');
-    fs.mkdirSync(userOpencodeDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(userOpencodeDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        tmux: {
-          enabled: true,
-          layout: 'main-vertical',
-          main_pane_size: 60,
-        },
-      }),
-    );
-
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        tmux: {
-          enabled: false, // Override enabled
-          layout: 'tiled', // Override layout
-        },
-      }),
-    );
-
-    const config = loadPluginConfig(projectDir);
-
-    expect(config.tmux?.enabled).toBe(false); // From project (override)
-    expect(config.tmux?.layout).toBe('tiled'); // From project
-    expect(config.tmux?.main_pane_size).toBe(60); // From user (preserved)
-  });
-
-  test("preserves user tmux.enabled when project doesn't specify", () => {
-    const userOpencodeDir = path.join(userConfigDir, 'opencode');
-    fs.mkdirSync(userOpencodeDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(userOpencodeDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        tmux: {
-          enabled: true,
-          layout: 'main-vertical',
-        },
-      }),
-    );
-
-    const projectDir = path.join(tempDir, 'project');
-    const projectConfigDir = path.join(projectDir, '.opencode');
-    fs.mkdirSync(projectConfigDir, { recursive: true });
-    fs.writeFileSync(
-      path.join(projectConfigDir, 'oh-my-opencode-slim.json'),
-      JSON.stringify({
-        agents: { oracle: { model: 'test' } }, // No tmux override
-      }),
-    );
-
-    const config = loadPluginConfig(projectDir);
-
-    expect(config.tmux?.enabled).toBe(true); // Preserved from user
-    expect(config.tmux?.layout).toBe('main-vertical'); // Preserved from user
-  });
-
   test('project config overrides top-level arrays', () => {
     const userOpencodeDir = path.join(userConfigDir, 'opencode');
     fs.mkdirSync(userOpencodeDir, { recursive: true });
@@ -1240,10 +1265,6 @@ describe('JSONC config support', () => {
             "explorer": { "model": "dev-explorer", },
           },
         },
-        "tmux": {
-          "enabled": true, // Enable tmux
-          "layout": "main-vertical",
-        },
       }`,
     );
 
@@ -1251,8 +1272,6 @@ describe('JSONC config support', () => {
     expect(config.preset).toBe('dev');
     expect(config.agents?.oracle?.model).toBe('dev-oracle');
     expect(config.agents?.explorer?.model).toBe('dev-explorer');
-    expect(config.tmux?.enabled).toBe(true);
-    expect(config.tmux?.layout).toBe('main-vertical');
   });
 });
 

+ 11 - 6
src/utils/env.test.ts

@@ -10,12 +10,17 @@ describe('isTruthyEnvValue', () => {
     expect(isTruthyEnvValue(value)).toBe(true);
   });
 
-  test.each([undefined, '', '0', 'false', 'no', 'off', 'anything'])(
-    '%p is not truthy',
-    (value) => {
-      expect(isTruthyEnvValue(value)).toBe(false);
-    },
-  );
+  test.each([
+    undefined,
+    '',
+    '0',
+    'false',
+    'no',
+    'off',
+    'anything',
+  ])('%p is not truthy', (value) => {
+    expect(isTruthyEnvValue(value)).toBe(false);
+  });
 });
 
 describe('isPluginDisabledByEnv', () => {