// MySQL 存储层自测:会往库里写几条 TEST- 开头的测试数据,跑完自动删掉 // 用法:node server/test-db.js(需要 config.js 里配好 db) const assert = require('assert') const store = require('./store.js') const { getPool, settings } = require('./db.js') const TEST_OPENID = 'oTestStoreUser' async function main() { if (store.backend !== 'mysql') { throw new Error('现在走的是 JSONL 后端:请在 config.js 里配好 db(库名 + 账号)再跑这个自测') } await store.ready() const outTradeNo = 'TEST' + Date.now() const createdAt = Date.now() try { await store.saveOrder({ outTradeNo, openid: TEST_OPENID, productId: '10001', env: 0, amount: 3900, status: 'created', createdAt, }) const created = await store.get(outTradeNo) assert.strictEqual(created.openid, TEST_OPENID) assert.strictEqual(created.amount, 3900) assert.strictEqual(created.code, '', '刚下单时不该有激活码') assert.strictEqual(created.createdAt, createdAt) // 时间列要是可读的 DATETIME(不是时间戳),且毫秒能原样存取 const [columnTypes] = await getPool().query( `SELECT DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'codes' AND COLUMN_NAME = 'created_at'`, [settings.database] ) assert.strictEqual(columnTypes[0].DATA_TYPE, 'datetime', '时间列应该是 DATETIME,不该是时间戳') const delivered = await store.assignCode(outTradeNo, { wxOrderId: 'wx-test-1', paidFee: 3900 }) assert.ok(/^[0-9A-HJKMNP-TV-Z]{4}(-[0-9A-HJKMNP-TV-Z]{4}){3}$/.test(delivered.code), delivered.code) assert.strictEqual(delivered.status, 'delivered') // 幂等:同一单再发一次还是同一张码(唯一索引挡住) const again = await store.assignCode(outTradeNo, {}) assert.strictEqual(again.code, delivered.code) let mine = await store.listByOpenid(TEST_OPENID) assert.deepStrictEqual(mine.map((o) => o.code), [delivered.code]) await store.markNotified(outTradeNo) assert.strictEqual((await store.get(outTradeNo)).notified, true) // 退款后码作废:从「我的」和待发货里都消失,但记录还在 await store.markRefunded(outTradeNo, 'R' + outTradeNo) mine = await store.listByOpenid(TEST_OPENID) assert.deepStrictEqual(mine, [], '退款后不该再出现在我的激活码里') const [codeRows] = await getPool().query('SELECT status FROM codes WHERE out_trade_no = ?', [outTradeNo]) assert.strictEqual(codeRows[0].status, 'revoked') // 价目表播种:至少有四个道具,价格和 MP 后台一致 const goods = await store.listGoods() assert.ok(goods.length >= 4, 'goods 表应该有播种的价目表') assert.strictEqual(goods.find((g) => g.productId === '10000').price, 9900) assert.strictEqual(goods.find((g) => g.productId === '90001').price, 10) // 文案配置表:默认值会播种进去,备注列告诉你在哪显示 const settings = await store.getSettings() assert.strictEqual(settings['buy.title'], '选择套餐') const [copyRows] = await getPool().query(`SELECT remark FROM settings WHERE cfg_key = 'buy.title'`) assert.strictEqual(copyRows[0].remark, '开通页的大标题') const stats = await store.stats() assert.ok(stats.orders >= 1) assert.ok(stats.users >= 1, '用户表应该有记录') // 用户表:首次使用时间建一次就不该再被覆盖,管理员标志默认 0 const [userRows] = await getPool().query('SELECT * FROM users WHERE openid = ?', [TEST_OPENID]) assert.strictEqual(userRows.length, 1) assert.strictEqual(userRows[0].is_admin, 0) const firstSeen = userRows[0].created_at.getTime() await store.touchUser(TEST_OPENID) const [againUsers] = await getPool().query('SELECT created_at, last_seen_at FROM users WHERE openid = ?', [TEST_OPENID]) assert.strictEqual(againUsers[0].created_at.getTime(), firstSeen, '首次使用时间不该被覆盖') assert.ok(againUsers[0].last_seen_at.getTime() >= firstSeen) // 管理员标志从 users.is_admin 读 assert.strictEqual((await store.getUser(TEST_OPENID)).isAdmin, false) await getPool().query('UPDATE users SET is_admin = 1 WHERE openid = ?', [TEST_OPENID]) assert.strictEqual((await store.getUser(TEST_OPENID)).isAdmin, true) console.log(`数据库自测通过(当前库里 ${stats.orders} 笔单 / ${stats.codes} 个码 / ${stats.users} 个用户)`) } finally { // 收尾:把测试数据删掉,别脏了真库 await getPool().query('DELETE FROM codes WHERE out_trade_no = ?', [outTradeNo]) await getPool().query('DELETE FROM orders WHERE out_trade_no = ?', [outTradeNo]) await getPool().query('DELETE FROM users WHERE openid = ?', [TEST_OPENID]) await getPool().end() } } main().catch((err) => { console.error('数据库自测失败:', err.message) process.exit(1) })