CtrlK
BlogDocsLog inGet started
Tessl Logo

ai-ecoverse/advanced-skills

Quarantined high-risk skills (browser session capture, WebSocket interception) for Slack and Microsoft Teams. Install only after reviewing the security implications.

72

Quality

90%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

slack-ext.test.jsskills/slack/tests/

// Tests for skills/slack/scripts/slack-ext.jsh
//
// Run with:
//   node --test skills/slack/tests/slack-ext.test.js
//
// Strategy: compile the real source (removing the trailing `await main()` so
// it does not auto-execute), inject mock sliccy:* modules and a stub browser,
// then call the exported internal functions directly. This exercises the REAL
// code — no duplicated reimplementation that could silently diverge.
//
// Mutation verification is documented inline: each test names the mutation that
// would break it, and the section at the bottom records the verification matrix.

const assert = require('node:assert/strict');
const test = require('node:test');
const fs = require('node:fs');
const path = require('node:path');
const { createRequire } = require('node:module');

const SCRIPT = path.resolve(__dirname, '../scripts/slack-ext.jsh');
const AsyncFunction = Object.getPrototypeOf(async function () {}).constructor;

// ── Test harness ──────────────────────────────────────────────────────────────

class NodeExitError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'NodeExitError';
    this.exitCode = code !== undefined ? code : 1;
  }
}

/**
 * Load the slack-ext.jsh module with mocked dependencies and simulated argv.
 *
 * @param {object} opts
 * @param {string[]} opts.argv     process.argv (after 'node script') words
 * @param {object}  [opts.user]   users.info response body (merged with defaults)
 * @param {object}  [opts.convs]  users.conversations response body
 * @param {object}  [opts.adminResult]  users.admin.* response body (default ok:true)
 * @param {object}  [opts.inviteResult] conversations.invite response body
 * @param {object}  [opts.kickResult]   conversations.kick response body
 * @param {string}  [opts.tabUrl] override the Slack tab URL
 */
async function load(opts) {
  const calls = [];
  const stdout = [];
  const stderr = [];

  // Default workspace in tab URL (auto-detection path)
  const tabUrl = opts.tabUrl || 'https://app.slack.com/client/T06DUTYDQ/C000';

  // Default user: a regular member (not bot, not restricted)
  const defaultUser = {
    id: 'U12345',
    name: 'testuser',
    real_name: 'Test User',
    is_bot: false,
    is_restricted: false,
    is_ultra_restricted: false,
    deleted: false,
    profile: { display_name: 'testuser' },
  };
  const resolvedUser = Object.assign({}, defaultUser, opts.user || {});

  const fakeTab = { id: 'tab1', url: tabUrl };

  const browserStub = {
    async findTab() {
      return fakeTab;
    },
    async localStorage(tab, key) {
      if (key === 'localConfig_v2') {
        return JSON.stringify({
          teams: { T06DUTYDQ: { token: 'xoxc-test-token' } },
        });
      }
      return null;
    },
    async fetch(tab, url, fetchOpts) {
      const method = url.replace('/api/', '');
      calls.push({ method, opts: fetchOpts });

      // users.info
      if (method === 'users.info') {
        return { body: { ok: true, user: resolvedUser } };
      }
      // users.conversations (for status guest channels)
      if (method === 'users.conversations') {
        if (opts.convs) return { body: opts.convs };
        return { body: { ok: true, channels: [] } };
      }
      // users.admin.setUltraRestricted
      if (method === 'users.admin.setUltraRestricted') {
        return { body: opts.adminResult || { ok: true } };
      }
      // users.admin.setRestricted
      if (method === 'users.admin.setRestricted') {
        return { body: opts.adminResult || { ok: true } };
      }
      // users.admin.setRegular
      if (method === 'users.admin.setRegular') {
        return { body: opts.adminResult || { ok: true } };
      }
      // conversations.invite
      if (method === 'conversations.invite') {
        return { body: opts.inviteResult || { ok: true } };
      }
      // conversations.kick
      if (method === 'conversations.kick') {
        return { body: opts.kickResult || { ok: true } };
      }
      return { body: { ok: false, error: 'not_mocked' } };
    },
  };

  const cliStub = {
    die(message, _options) {
      throw new NodeExitError(String(message), 1);
    },
    help(message) {
      stdout.push(String(message));
      throw new NodeExitError('help', 0);
    },
    out(value) {
      stdout.push(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
    },
    warn(message) {
      stderr.push(String(message));
    },
  };

  const colorStub = new Proxy(
    {},
    {
      get: () => (s) => String(s),
    }
  );

  const mocks = {
    'sliccy:browser': browserStub,
    'sliccy:cli': cliStub,
    'sliccy:color': colorStub,
    'sliccy:exec': { exec: async () => ({ exitCode: 0, stdout: '', stderr: '' }) },
    // The `app` manifest subcommands (added later on the same branch) require
    // sliccy:http and sliccy:skill at module scope. They are stubbed to THROW if
    // a user-management command ever reaches them: these commands must keep
    // going through browser.fetch with the xoxc session token, never through the
    // bearer-token App Manifest client. See tests/slack-ext-app.test.js for the
    // app-command harness.
    'sliccy:http': {
      client: () => ({
        post: async () => {
          throw new Error('user-management commands must not use the App Manifest HTTP client');
        },
      }),
    },
    'sliccy:skill': { config: async () => null },
    fs: {
      readFile: async () => {
        throw new Error('ENOENT');
      },
      writeFile: async () => {
        throw new Error('EACCES');
      },
    },
  };

  // Relative specifiers (./argv.js, ./manifest-diff.js) load the REAL extracted
  // module from disk so the node:test suite exercises the same code as production.
  const scriptRequire = createRequire(SCRIPT);
  const mockRequire = (id) => {
    if (Object.prototype.hasOwnProperty.call(mocks, id)) return mocks[id];
    if (id.startsWith('./') || id.startsWith('../')) return scriptRequire(id);
    throw new Error('unexpected require(' + id + ')');
  };

  // Strip the trailing `await main()` so the module does not auto-execute
  let source = fs.readFileSync(SCRIPT, 'utf8');
  // Anchor on the actual trailer (`try { await main(); }`), NOT on the first
  // top-level `try {` in the file. The greedy form truncated the module at the
  // first top-level try block, which silently discarded ~1800 lines and made
  // every test fail with '<fn> is not defined'.
  source = source.replace(/\ntry \{\s*\n\s*await main\(\);[\s\S]*$/, '\n');

  // Append exports of the key internal functions for direct testing
  source += `
return {
  parseArgv,
  userTypeLabel,
  buildSetUltraRestrictedParams,
  buildSetRestrictedParams,
  buildSetRegularParams,
  cmdStatus,
  cmdSetSingle,
  cmdSetMulti,
  cmdSetMember,
  cmdAddChannel,
  cmdRemoveChannel,
};
`;

  const mockProcess = {
    argv: ['node', SCRIPT, ...opts.argv],
    env: {},
    exit: (code) => {
      throw new NodeExitError('exit', code);
    },
  };

  const mockConsole = {
    log: (msg) => stdout.push(String(msg === undefined ? '' : msg)),
    error: (msg) => stderr.push(String(msg === undefined ? '' : msg)),
    warn: (msg) => stderr.push(String(msg === undefined ? '' : msg)),
  };

  const factory = new AsyncFunction('require', 'process', 'console', source);
  const mod = await factory(mockRequire, mockProcess, mockConsole);

  return {
    mod,
    calls,
    stdout,
    stderr,
    text: () => stdout.join('\n'),
    errText: () => stderr.join('\n'),
    apiCalls: () => calls.map((c) => c.method),
    adminCalls: () => calls.filter((c) => c.method.startsWith('users.admin.')),
    mutatingCalls: () =>
      calls.filter(
        (c) =>
          c.method.startsWith('users.admin.') ||
          c.method === 'conversations.invite' ||
          c.method === 'conversations.kick'
      ),
  };
}

// ── Pure function tests (no browser mocks needed, extracted directly) ─────────

test('userTypeLabel: regular member', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { userTypeLabel } = h.mod;
  assert.equal(userTypeLabel({ is_bot: false, is_restricted: false, is_ultra_restricted: false, deleted: false }), 'regular');
});

test('userTypeLabel: multi-channel guest', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { userTypeLabel } = h.mod;
  assert.equal(userTypeLabel({ is_bot: false, is_restricted: true, is_ultra_restricted: false, deleted: false }), 'multi-channel guest');
});

test('userTypeLabel: single-channel guest', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { userTypeLabel } = h.mod;
  assert.equal(userTypeLabel({ is_bot: false, is_restricted: true, is_ultra_restricted: true, deleted: false }), 'single-channel guest');
});

test('userTypeLabel: bot', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { userTypeLabel } = h.mod;
  assert.equal(userTypeLabel({ is_bot: true, is_restricted: false, is_ultra_restricted: false, deleted: false }), 'bot');
});

test('userTypeLabel: deactivated', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { userTypeLabel } = h.mod;
  assert.equal(userTypeLabel({ is_bot: false, is_restricted: false, is_ultra_restricted: false, deleted: true }), 'deactivated');
});

// ── Parameter name tests — these are the most safety-critical ─────────────────

test('buildSetUltraRestrictedParams uses "channel" (singular), not "channels"', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { buildSetUltraRestrictedParams } = h.mod;
  const params = buildSetUltraRestrictedParams('U123', 'C456', 'T789');

  // CRITICAL: the API requires `channel` (singular). Passing `channels` returns
  // invalid_arguments. Verified live 2026-09-18.
  assert.equal(params.channel, 'C456', 'must use "channel" (singular)');
  assert.equal(params.channels, undefined, 'must NOT have "channels" (plural)');
  assert.equal(params.user, 'U123');
  assert.equal(params.team_id, 'T789');
});

test('buildSetRestrictedParams includes user and team_id', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { buildSetRestrictedParams } = h.mod;
  const params = buildSetRestrictedParams('U123', 'T789');
  assert.equal(params.user, 'U123');
  assert.equal(params.team_id, 'T789');
  // setRestricted does NOT take a channel parameter
  assert.equal(params.channel, undefined);
});

test('buildSetRegularParams includes user and team_id', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { buildSetRegularParams } = h.mod;
  const params = buildSetRegularParams('U123', 'T789');
  assert.equal(params.user, 'U123');
  assert.equal(params.team_id, 'T789');
  assert.equal(params.channel, undefined);
});

// ── --confirm guard tests ─────────────────────────────────────────────────────

test('set-single without --confirm makes no admin API call', async () => {
  let h;
  try {
    h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456'],
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) { /* ok */ } else throw e;
  }
  assert.equal(h.adminCalls().length, 0, 'no admin API call without --confirm');
  const text = h.text();
  assert.match(text, /no --confirm|nothing changed/i, 'should mention --confirm required');
});

test('set-multi without --confirm makes no admin API call', async () => {
  let h;
  try {
    h = await load({ argv: ['--ws=T06DUTYDQ', 'set-multi', 'U12345'] });
    await h.mod.cmdSetMulti();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) { /* ok */ } else throw e;
  }
  assert.equal(h.adminCalls().length, 0, 'no admin API call without --confirm');
});

test('set-member without --confirm makes no admin API call', async () => {
  let h;
  try {
    h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-member', 'U12345'],
      user: { is_restricted: true },
    });
    await h.mod.cmdSetMember();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) { /* ok */ } else throw e;
  }
  assert.equal(h.adminCalls().length, 0, 'no admin API call without --confirm');
});

test('add-channel without --confirm makes no mutating API call', async () => {
  let h;
  try {
    h = await load({ argv: ['--ws=T06DUTYDQ', 'add-channel', 'U12345', '--channel=C456'] });
    await h.mod.cmdAddChannel();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) { /* ok */ } else throw e;
  }
  assert.equal(h.mutatingCalls().length, 0, 'no mutating call without --confirm');
});

test('remove-channel without --confirm makes no mutating API call', async () => {
  let h;
  try {
    h = await load({ argv: ['--ws=T06DUTYDQ', 'remove-channel', 'U12345', '--channel=C456'] });
    await h.mod.cmdRemoveChannel();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) { /* ok */ } else throw e;
  }
  assert.equal(h.mutatingCalls().length, 0, 'no mutating call without --confirm');
});

// ── Bot user rejection tests ──────────────────────────────────────────────────

test('set-single refuses bot user', async () => {
  let err;
  try {
    const h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm'],
      user: { is_bot: true, is_restricted: false, is_ultra_restricted: false, deleted: false },
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    err = e;
  }
  assert.ok(err, 'should have thrown');
  assert.equal(err.name, 'NodeExitError');
  assert.match(err.message, /bot/i, 'error message should mention bot');
});

test('set-multi refuses bot user', async () => {
  let err;
  try {
    const h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-multi', 'U12345', '--confirm'],
      user: { is_bot: true, is_restricted: false, is_ultra_restricted: false, deleted: false },
    });
    await h.mod.cmdSetMulti();
  } catch (e) {
    err = e;
  }
  assert.ok(err, 'should have thrown');
  assert.equal(err.name, 'NodeExitError');
  assert.match(err.message, /bot/i);
});

test('set-member refuses bot user', async () => {
  let err;
  try {
    const h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-member', 'U12345', '--confirm'],
      user: { is_bot: true, is_restricted: false, is_ultra_restricted: false, deleted: false },
    });
    await h.mod.cmdSetMember();
  } catch (e) {
    err = e;
  }
  assert.ok(err, 'should have thrown');
  assert.equal(err.name, 'NodeExitError');
  assert.match(err.message, /bot/i);
});

// ── Already-in-state tests ────────────────────────────────────────────────────


test('set-multi is no-op when user is already a multi-channel guest', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-multi', 'U12345', '--confirm'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
  });
  await h.mod.cmdSetMulti();
  assert.equal(h.adminCalls().length, 0, 'should not call setRestricted for already-MCG');
  assert.match(h.text(), /no change needed/i);
});

test('set-member is no-op when user is already a regular member', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-member', 'U12345', '--confirm'],
    user: { is_restricted: false, is_ultra_restricted: false, deleted: false, is_bot: false },
  });
  await h.mod.cmdSetMember();
  assert.equal(h.adminCalls().length, 0, 'should not call setRegular for already-member');
  assert.match(h.text(), /no change needed/i);
});

// ── Confirmed mutation tests ──────────────────────────────────────────────────

test('set-single with --confirm calls users.admin.setUltraRestricted with channel (singular)', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm'],
  });
  await h.mod.cmdSetSingle();

  const adminCall = h.calls.find((c) => c.method === 'users.admin.setUltraRestricted');
  assert.ok(adminCall, 'users.admin.setUltraRestricted must be called');

  // Verify the URLSearchParams body contains 'channel' (singular)
  const body = adminCall.opts && adminCall.opts.body;
  assert.ok(body, 'call must have a body');
  assert.match(body, /channel=C456/, 'body must contain channel=C456');
  assert.doesNotMatch(body, /channels=/, 'body must NOT contain channels= (plural)');
  assert.match(body, /user=U12345/, 'body must contain user=U12345');
  assert.match(body, /team_id=T06DUTYDQ/, 'body must contain team_id=T06DUTYDQ');
});

test('set-multi with --confirm calls users.admin.setRestricted', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-multi', 'U12345', '--confirm'],
  });
  await h.mod.cmdSetMulti();

  const adminCall = h.calls.find((c) => c.method === 'users.admin.setRestricted');
  assert.ok(adminCall, 'users.admin.setRestricted must be called');
  const body = adminCall.opts && adminCall.opts.body;
  assert.match(body, /user=U12345/);
  assert.match(body, /team_id=T06DUTYDQ/);
});

test('set-member with --confirm calls users.admin.setRegular', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-member', 'U12345', '--confirm'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
  });
  await h.mod.cmdSetMember();

  const adminCall = h.calls.find((c) => c.method === 'users.admin.setRegular');
  assert.ok(adminCall, 'users.admin.setRegular must be called');
  const body = adminCall.opts && adminCall.opts.body;
  assert.match(body, /user=U12345/);
  assert.match(body, /team_id=T06DUTYDQ/);
});

test('add-channel with --confirm calls conversations.invite', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'add-channel', 'U12345', '--channel=C456', '--confirm'],
  });
  await h.mod.cmdAddChannel();

  const inviteCall = h.calls.find((c) => c.method === 'conversations.invite');
  assert.ok(inviteCall, 'conversations.invite must be called');
  const body = inviteCall.opts && inviteCall.opts.body;
  assert.match(body, /channel=C456/);
  assert.match(body, /users=U12345/);
});

test('remove-channel with --confirm calls conversations.kick', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'remove-channel', 'U12345', '--channel=C456', '--confirm'],
  });
  await h.mod.cmdRemoveChannel();

  const kickCall = h.calls.find((c) => c.method === 'conversations.kick');
  assert.ok(kickCall, 'conversations.kick must be called');
  const body = kickCall.opts && kickCall.opts.body;
  assert.match(body, /channel=C456/);
  assert.match(body, /user=U12345/);
});

// ── parseArgv: BOOL_FLAGS includes confirm ────────────────────────────────────

test('parseArgv treats --confirm as a boolean flag (no value consumed)', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { parseArgv } = h.mod;

  // Without --confirm
  const a = parseArgv(['set-single', 'U1', '--channel=C2', '--ws=T3']);
  assert.equal(a.flags.confirm, undefined);

  // With --confirm as the last flag (no next token)
  const b = parseArgv(['set-single', 'U1', '--channel=C2', '--ws=T3', '--confirm']);
  assert.equal(b.flags.confirm, true);

  // With --confirm followed by a positional — must not consume the positional as value
  const c = parseArgv(['set-single', '--confirm', 'U1']);
  assert.equal(c.flags.confirm, true);
  assert.deepEqual(c.positional, ['set-single', 'U1']);
});

// ── Mutation test documentation ────────────────────────────────────────────────
//
// Each mutation and the test that catches it:
//
// MUTATION 1: Remove the `--confirm` guard (delete `if (!flags.confirm) { ... return; }`)
//   Caught by: "set-single without --confirm makes no admin API call"
//              "set-multi without --confirm makes no admin API call"
//              "set-member without --confirm makes no admin API call"
//              "add-channel without --confirm makes no mutating API call"
//              "remove-channel without --confirm makes no mutating API call"
//
// MUTATION 2: Change `channel` to `channels` in buildSetUltraRestrictedParams
//   Caught by: "buildSetUltraRestrictedParams uses "channel" (singular), not "channels""
//              "set-single with --confirm calls users.admin.setUltraRestricted with channel (singular)"
//
// MUTATION 3: Remove the bot check (delete `if (user.is_bot) { cli.die(...) }`)
//   Caught by: "set-single refuses bot user"
//              "set-multi refuses bot user"
//              "set-member refuses bot user"
//
// MUTATION 4: Remove the already-in-state check for set-single
//   Caught by: "set-single: guest already in B with --channel=B is a no-op"
//   NOTE: the original test here asserted the no-op from account type ALONE, with no
//   convs fixture, which is the very defect Codex finding 2 reported. It was removed;
//   the superseding test supplies the current channel and asserts the true no-op.
//
// MUTATION 5: Remove the already-in-state check for set-multi
//   Caught by: "set-multi is no-op when user is already a multi-channel guest"
//
// MUTATION 6: Remove the already-in-state check for set-member
//   Caught by: "set-member is no-op when user is already a regular member"
//
// MUTATION 7: Call wrong Slack method in set-single (e.g. setRestricted instead)
//   Caught by: "set-single with --confirm calls users.admin.setUltraRestricted with channel (singular)"
//
// MUTATION 8: Call wrong Slack method in set-multi (e.g. setRegular instead)
//   Caught by: "set-multi with --confirm calls users.admin.setRestricted"
//
// MUTATION 9: Call wrong Slack method in set-member (e.g. setRestricted instead)
//   Caught by: "set-member with --confirm calls users.admin.setRegular"
//
// VERIFICATION EVIDENCE (mutation tests run before filing the PR):
//   All mutations above were applied, the named test failed, and the mutation was reverted.

// ── Finding 1: --confirm=value bool normalization ─────────────────────────────

test('--confirm=false does NOT authorize a mutation (admin call must not happen)', async () => {
  // The string "false" is truthy; before the fix it bypassed the --confirm guard.
  // After the fix, --confirm=false is boolean false and the guard blocks the call.
  let h;
  try {
    h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm=false'],
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) {
      /* expected: dry-run exit */
    } else {
      throw e;
    }
  }
  assert.equal(
    h.adminCalls().length,
    0,
    '--confirm=false must NOT issue the admin API call'
  );
});

test('--confirm=false dry-run still mentions what would happen', async () => {
  let h;
  try {
    h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm=false'],
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    if (e.name !== 'NodeExitError' || e.exitCode !== 0) throw e;
  }
  assert.ok(
    /would change|no --confirm|nothing changed/i.test(h.text()),
    'dry-run output must describe the would-be change'
  );
});

test('--confirm=true (equals form) DOES authorize the mutation', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm=true'],
  });
  await h.mod.cmdSetSingle();
  assert.equal(
    h.adminCalls().length,
    1,
    '--confirm=true must issue the admin API call'
  );
});

test('--confirm=fasle (typo) is a fatal error, not an authorization', async () => {
  // A typo in the value must never authorize a mutation. It must be a fatal
  // error so the operator knows the flag was not understood.
  let err;
  try {
    await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm=fasle'],
    });
  } catch (e) {
    err = e;
  }
  assert.ok(err, 'should have thrown an error');
  assert.equal(err.name, 'NodeExitError', 'must exit non-zero');
  assert.ok(err.exitCode !== 0, 'exit code must be non-zero');
});

test('parseArgv: --confirm=false stores false (not truthy string "false")', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { parseArgv } = h.mod;
  const r = parseArgv(['set-single', 'U1', '--confirm=false']);
  assert.strictEqual(r.flags.confirm, false, '--confirm=false must be stored as boolean false');
});

test('parseArgv: --confirm=true stores true', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { parseArgv } = h.mod;
  const r = parseArgv(['set-single', 'U1', '--confirm=true']);
  assert.strictEqual(r.flags.confirm, true, '--confirm=true must be stored as boolean true');
});

test('parseArgv: --confirm=yes, --confirm=1, --confirm=on all store true', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { parseArgv } = h.mod;
  assert.strictEqual(parseArgv(['x', '--confirm=yes']).flags.confirm, true);
  assert.strictEqual(parseArgv(['x', '--confirm=1']).flags.confirm, true);
  assert.strictEqual(parseArgv(['x', '--confirm=on']).flags.confirm, true);
});

test('parseArgv: --confirm=no, --confirm=0, --confirm=off all store false', async () => {
  const h = await load({ argv: ['status', 'U1'] });
  const { parseArgv } = h.mod;
  assert.strictEqual(parseArgv(['x', '--confirm=no']).flags.confirm, false);
  assert.strictEqual(parseArgv(['x', '--confirm=0']).flags.confirm, false);
  assert.strictEqual(parseArgv(['x', '--confirm=off']).flags.confirm, false);
});

// ── Finding 2: set-single already-SCG channel comparison ─────────────────────

test('set-single: guest already in A with --channel=B attempts the change', async () => {
  // The guest is currently in C_OLD. We request C_NEW. This is a real change;
  // setUltraRestricted MUST be called.
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=CNEW', '--confirm'],
    user: { is_restricted: true, is_ultra_restricted: true, deleted: false, is_bot: false },
    convs: { ok: true, channels: [{ id: 'COLD', name: 'old-channel' }] },
  });
  await h.mod.cmdSetSingle();
  assert.equal(
    h.adminCalls().length,
    1,
    'must call setUltraRestricted when the requested channel differs from the current one'
  );
  const adminCall = h.calls.find((c) => c.method === 'users.admin.setUltraRestricted');
  assert.ok(adminCall, 'call must be setUltraRestricted');
  assert.ok(/channel=CNEW/.test(adminCall.opts.body), 'must use the new channel ID');
});

test('set-single: guest already in B with --channel=B is a no-op', async () => {
  // The guest is already in the exact same channel. This is a true no-op.
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=CSAME', '--confirm'],
    user: { is_restricted: true, is_ultra_restricted: true, deleted: false, is_bot: false },
    convs: { ok: true, channels: [{ id: 'CSAME', name: 'same-channel' }] },
  });
  await h.mod.cmdSetSingle();
  assert.equal(h.adminCalls().length, 0, 'must NOT call setUltraRestricted when channel unchanged');
  assert.ok(/no change needed/i.test(h.text()), 'must say no change needed');
});

test('set-single: failed users.conversations lookup is fatal, not "no change needed"', async () => {
  // If we cannot determine the current channel, we must not claim anything about
  // the account state. Claiming "no change needed" on a failed API call is
  // dangerous — it could mask a real difference.
  let err;
  try {
    const h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=C456', '--confirm'],
      user: { is_restricted: true, is_ultra_restricted: true, deleted: false, is_bot: false },
      convs: { ok: false, error: 'enterprise_is_restricted' },
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    err = e;
  }
  assert.ok(err, 'should have thrown');
  assert.equal(err.name, 'NodeExitError');
  // Must NOT print "no change needed" — that would be a false claim
  // (We can't check h.text() here since we don't have h in scope; the throw
  //  itself is the proof that the command did not silently succeed or no-op.)
});

test('set-single: channel-change path respects --confirm gate', async () => {
  // Even when a channel change is needed, the --confirm gate must block the call.
  let h;
  try {
    h = await load({
      argv: ['--ws=T06DUTYDQ', 'set-single', 'U12345', '--channel=CNEW'],
      user: { is_restricted: true, is_ultra_restricted: true, deleted: false, is_bot: false },
      convs: { ok: true, channels: [{ id: 'COLD', name: 'old-channel' }] },
    });
    await h.mod.cmdSetSingle();
  } catch (e) {
    if (e.name === 'NodeExitError' && e.exitCode === 0) {
      /* expected dry-run exit */
    } else {
      throw e;
    }
  }
  assert.equal(
    h.adminCalls().length,
    0,
    'channel-change path must still require --confirm'
  );
});

// ── Finding 3: cmdStatus users.conversations failure handling ─────────────────

test('status: failed users.conversations does NOT print "(none found)"', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'status', 'U12345'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
    convs: { ok: false, error: 'enterprise_is_restricted' },
  });
  await h.mod.cmdStatus();
  const out = h.text();
  assert.ok(
    !/(none found)/i.test(out),
    'must NOT print "(none found)" when the API call failed'
  );
});

test('status: failed users.conversations prints the actual Slack error code', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'status', 'U12345'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
    convs: { ok: false, error: 'enterprise_is_restricted' },
  });
  await h.mod.cmdStatus();
  const out = h.text();
  assert.ok(
    /enterprise_is_restricted/.test(out),
    'must print the actual Slack error code so the user knows what went wrong'
  );
});

test('status: success with zero channels prints a distinct empty-state message', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'status', 'U12345'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
    convs: { ok: true, channels: [] },
  });
  await h.mod.cmdStatus();
  const out = h.text();
  assert.ok(
    !/(none found)/i.test(out),
    'empty-success path must use a different message from the old "(none found)" bucket'
  );
  // Should say something about "none" or "no channel" but NOT "(none found)"
  assert.ok(
    /none|no channel/i.test(out),
    'should still describe the empty state'
  );
});

test('status --json: failed users.conversations produces channels_error in output', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'status', 'U12345', '--json'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
    convs: { ok: false, error: 'enterprise_is_restricted' },
  });
  await h.mod.cmdStatus();
  // Find the JSON line in stdout
  const jsonLine = h.stdout.find((l) => {
    try { JSON.parse(l); return true; } catch (e) { return false; }
  });
  assert.ok(jsonLine, 'should have a JSON output line');
  const parsed = JSON.parse(jsonLine);
  assert.ok(
    parsed.channels_error === 'enterprise_is_restricted',
    'channels_error must be present and equal to the Slack error code'
  );
});

test('status --json: success with channels produces channels array in output', async () => {
  const h = await load({
    argv: ['--ws=T06DUTYDQ', 'status', 'U12345', '--json'],
    user: { is_restricted: true, is_ultra_restricted: false, deleted: false, is_bot: false },
    convs: { ok: true, channels: [{ id: 'C111', name: 'chan1' }] },
  });
  await h.mod.cmdStatus();
  const jsonLine = h.stdout.find((l) => {
    try { JSON.parse(l); return true; } catch (e) { return false; }
  });
  assert.ok(jsonLine, 'should have JSON output');
  const parsed = JSON.parse(jsonLine);
  assert.ok(Array.isArray(parsed.channels), 'channels must be an array in JSON output');
  assert.equal(parsed.channels[0].id, 'C111', 'channel id must match');
});

// ── Updated mutation matrix (additions for the three findings) ────────────────
//
// MUTATION 10: In parseArgv, do NOT normalize BOOL_FLAGS in the --name=value branch
//   (i.e. keep `f[m[1]] = m[2]` for all names, even bool ones)
//   Caught by: "--confirm=false does NOT authorize a mutation"
//              "parseArgv: --confirm=false stores false (not truthy string)"
//
// MUTATION 11: In parseArgv, accept --confirm=fasle (typo) silently as true
//   Caught by: "--confirm=fasle (typo) is a fatal error"
//
// MUTATION 12: In set-single, keep the old "return immediately on is_ultra_restricted"
//   without channel comparison
//   Caught by: "set-single: guest already in A with --channel=B attempts the change"
//
// MUTATION 13: In set-single, call users.conversations but skip the fatal-error branch
//   for !scgConv.ok (treat it as no change needed instead)
//   Caught by: "set-single: failed users.conversations lookup is fatal"
//
// MUTATION 14: In cmdStatus, collapse !ok and ok+empty back into a single else branch
//   Caught by: "status: failed users.conversations does NOT print (none found)"
//              "status: failed users.conversations prints the actual Slack error code"
//
// MUTATION 15: In cmdStatus, omit channels_error from the JSON output on failure
//   Caught by: "status --json: failed users.conversations produces channels_error"
//
// VERIFICATION: mutations 10-15 were each applied, the named test was confirmed to fail,
// then the mutation was reverted. See report for details.

tile.json