// The bot→app internal client (TEAMS.md §7.1). // // One property carries this file: the base URL is DERIVED from // `SITE_INTERNAL_URL`, which already names the app's internal listener with a // path on the end. That derivation is the reason every existing deployment gains // slash commands with no compose change, and it is exactly the kind of string // handling that breaks silently — a wrong base means "the app is down" forever, // with nothing in the logs but a fetch error. const { test, beforeEach, afterEach } = require('node:test') const assert = require('node:assert/strict') const env = { ...process.env } const realFetch = global.fetch beforeEach(() => { process.env.SITE_INTERNAL_URL = 'http://app:3001/internal/bot-config' process.env.BOT_INTERNAL_KEY = 'shh' delete require.cache[require.resolve('../src/site/appInternalClient')] }) afterEach(() => { process.env = { ...env } global.fetch = realFetch }) /** Load the client fresh and record the single fetch it makes. */ function withFetch(response) { const seen = {} global.fetch = async (url, init) => { seen.url = url seen.init = init return response } // eslint-disable-next-line global-require return { client: require('../src/site/appInternalClient'), seen } } const ok = (body) => ({ ok: true, status: 200, json: async () => body }) test('the commands URL is the internal listener’s origin, not its bot-config path', async () => { const { client, seen } = withFetch(ok({ version: 3, commands: [] })) const res = await client.fetchCommands() assert.equal(seen.url, 'http://app:3001/internal/commands') assert.equal(seen.init.headers['X-Internal-Key'], 'shh') assert.deepEqual(res.data, { version: 3, commands: [] }) }) test('a dispatch names the platform, so the app never has to guess', async () => { const { client, seen } = withFetch(ok({ ok: true, response: {} })) await client.dispatchCommand({ command: 'guild', options: { name: 'KOC' }, platformUserId: '5', guildId: '9' }) assert.equal(seen.url, 'http://app:3001/internal/commands/dispatch') assert.deepEqual(JSON.parse(seen.init.body), { command: 'guild', options: { name: 'KOC' }, platform: 'discord', platformUserId: '5', guildId: '9', }) }) // A bot with no internal URL configured is an ordinary deployment state (the // warning already exists in bootstrap.js); it must not become an exception on // every `ready`. test('an unconfigured or unparseable SITE_INTERNAL_URL is a refusal, not a throw', async () => { delete process.env.SITE_INTERNAL_URL const { client } = withFetch(ok({})) assert.equal((await client.fetchCommands()).ok, false) delete require.cache[require.resolve('../src/site/appInternalClient')] process.env.SITE_INTERNAL_URL = 'not a url' // eslint-disable-next-line global-require assert.equal((await require('../src/site/appInternalClient').fetchCommands()).ok, false) }) test('a non-2xx carries its status so the caller can tell "down" from "rejected"', async () => { const { client } = withFetch({ ok: false, status: 401, json: async () => ({}) }) const res = await client.fetchCommands() assert.equal(res.ok, false) assert.equal(res.status, 401) })