jest.mock('mysql2/promise', () => ({ createPool: jest.fn(), createConnection: jest.fn() })); const mysql = require('mysql2/promise'); const db = require('../src/services/databaseService'); afterEach(() => jest.restoreAllMocks()); test('normal clean startup creates auth schema idempotently without credential writes', async () => { process.env.DB_PASSWORD = 'test-only'; const execute = jest.fn(async sql => { if (/INFORMATION_SCHEMA\.TABLES/i.test(sql)) return [[{ ENGINE: 'InnoDB' }]]; if (/COUNT\(\*\).*auth_accounts/is.test(sql)) return [[{ account_count: 1 }]]; return [[]]; }); mysql.createPool.mockReturnValue({ execute, getConnection: async () => ({ query: async () => [[]], release() {} }) }); for (const method of ['ensureSystemSettingsTable', 'removeLegacySupportSecrets', 'syncSupportSettingsFromEnv', 'createTables', 'seedAiFeatureSettings']) jest.spyOn(db, method).mockResolvedValue(); await db.initialize(); await db.initialize(); expect(execute.mock.calls).toHaveLength(8); for (const [sql] of execute.mock.calls) { expect(sql).not.toMatch(/INSERT|UPDATE auth_accounts|REPLACE/i); } expect(execute.mock.calls.filter(([sql]) => /CREATE TABLE IF NOT EXISTS auth_accounts/i.test(sql))).toHaveLength(2); expect(execute.mock.calls.filter(([sql]) => /CREATE TABLE IF NOT EXISTS ordrestyring_offer_operations/i.test(sql))).toHaveLength(2); }); test('provisions the first admin once from configured credentials without overwriting accounts', async () => { const execute = jest.fn() .mockResolvedValueOnce([[{ account_count: 0 }]]) .mockResolvedValueOnce([{ affectedRows: 1 }]); const hashPassword = jest.fn().mockResolvedValue('$2b$12$initial-admin-hash'); const { provisionInitialAdmin } = require('../src/services/authAccountSchema'); await expect(provisionInitialAdmin({ execute }, { env: { AUTH_USERNAME: 'operator', AUTH_PASSWORD: 'configured-secret' }, hashPassword })).resolves.toBe(true); expect(hashPassword).toHaveBeenCalledWith('configured-secret', 12); expect(execute.mock.calls[1]).toEqual([ expect.stringMatching(/INSERT IGNORE INTO auth_accounts/), ['operator', '$2b$12$initial-admin-hash', 'admin'] ]); }); test('fails closed when an empty account table has no initial admin credentials', async () => { const { provisionInitialAdmin } = require('../src/services/authAccountSchema'); const execute = jest.fn().mockResolvedValueOnce([[{ account_count: 0 }]]); await expect(provisionInitialAdmin({ execute }, { env: {}, hashPassword: jest.fn() })) .rejects.toMatchObject({ code: 'INITIAL_ADMIN_REQUIRED' }); expect(execute).toHaveBeenCalledTimes(1); }); test('converts a legacy non-transactional auth table before account mutations can run', async () => { const execute = jest.fn(async sql => ( /INFORMATION_SCHEMA\.TABLES/i.test(sql) ? [[{ ENGINE: 'MyISAM' }]] : [[]] )); await require('../src/services/authAccountSchema').ensureAuthAccountsTable({ execute }); expect(execute.mock.calls.map(([sql]) => sql)).toEqual([ expect.stringMatching(/CREATE TABLE IF NOT EXISTS auth_accounts/i), expect.stringMatching(/INFORMATION_SCHEMA\.TABLES/i), expect.stringMatching(/ALTER TABLE auth_accounts ENGINE=InnoDB/i) ]); }); test('migration has no credential seeding or hashing dependency', () => { const source = require('fs').readFileSync(require('path').join(__dirname, '../../database/migrations/20260904_users_table.js'), 'utf8'); expect(source).not.toMatch(/bcrypt|AUTH_PASSWORD|INSERT|seedUsers/); }); test('migration executes only idempotent DDL even when legacy credentials are configured', async () => { const fs = require('fs'); const path = require('path'); const vm = require('vm'); const filename = path.join(__dirname, '../../database/migrations/20260904_users_table.js'); const execute = jest.fn(async sql => ( /INFORMATION_SCHEMA\.TABLES/i.test(sql) ? [[{ ENGINE: 'InnoDB' }]] : [[]] )); let finished; const done = new Promise(resolve => { finished = resolve; }); const connection = { execute, end: jest.fn(async () => finished()) }; const localRequire = name => { if (name === 'mysql2/promise') return { createConnection: async () => connection }; if (name === 'dotenv') return { config() {} }; if (name.includes('authAccountSchema')) return require('../src/services/authAccountSchema'); return require(name); }; vm.runInNewContext(fs.readFileSync(filename, 'utf8'), { require: localRequire, __dirname: path.dirname(filename), console, process: { env: { AUTH_USERNAME: 'operator', AUTH_PASSWORD: 'must-not-be-written' }, exit: jest.fn() } }); await done; expect(execute).toHaveBeenCalledTimes(2); expect(execute.mock.calls[0][0]).toMatch(/CREATE TABLE IF NOT EXISTS auth_accounts/); expect(execute.mock.calls[0]).toHaveLength(1); });