MatchMaster/server/laf-cloud/tests/login-ios-reborn.test.mjs
2026-09-30 22:01:37 +08:00

90 lines
5.0 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { stripTypeScriptTypes } from 'node:module';
// Exercise the real login recovery function without contacting the payment provider.
const source = await readFile(new URL('../functions/login.ts', import.meta.url), 'utf8');
const recoverySource = source.slice(source.indexOf('async function getIosOrder('), source.indexOf('async function sead('));
const buildRecovery = new Function('db', 'getOrder', 'sead', 'getClientIp', 'JungleConfig', `return (${stripTypeScriptTypes(recoverySource)});`);
function fixture(itemid, extra = {}, providerState = 'SUCCESS') {
const user = { _id: 'user-1', openid: 'openid-1', rebornGiftTime: 0, rebornGiftCount: 3, pay_user: false };
const order = { outTradeNo: 'order-1', openid: user.openid, state: 0, type: 'ios', itemid, ...extra };
const providerCalls = [];
const db = { collection(name) {
const documents = name === 'users' ? [user] : [order];
return { where(query) {
const matched = documents.filter(row => Object.entries(query).every(([key, value]) => row[key] === value));
return {
async get() { return { data: structuredClone(matched) }; },
async getOne() { return { data: structuredClone(matched[0]) }; },
async update(patch) { matched.forEach(row => Object.assign(row, patch)); },
async remove() { assert.fail('recoverable activity orders must not be deleted'); },
};
} };
} };
const recover = buildRecovery(db, async orderId => {
providerCalls.push(orderId);
return { trade_state: providerState };
}, () => {}, () => '', { getJunglePaidTierByProductId: () => null });
return { user, order, providerCalls, recover };
}
for (const itemid of ['reborn_Gift', 'reborn_Gift_2', 'reborn_Gift_3', 'reborn_Gift_4', 'reborn_Gift_5']) {
test(`login preserves main iOS recovery for ${itemid} until client confirms delivery`, async () => {
const { user, order, providerCalls, recover } = fixture(itemid);
const before = Date.now();
const pending = await recover(user.openid, {});
assert.equal(user.rebornGiftCount, 0);
assert(user.rebornGiftTime >= before);
assert.equal(user.pay_user, true);
assert.equal(order.state, 1, 'confirmed payment stays pending for client reward delivery');
assert.deepEqual(pending.map(row => row.outTradeNo), ['order-1']);
assert.deepEqual(providerCalls, ['order-1']);
assert.deepEqual(await recover(user.openid, {}), [], 'state=1 goes through ordinary login recovery next time');
assert.deepEqual(providerCalls, ['order-1'], 'no second provider query for confirmed payment');
});
}
test('disabled gold miner orders never enter legacy iOS recovery', async () => {
const { user, order, providerCalls, recover } = fixture('gold_miner', { goldMiner: { confirmedAt: 0 }, time: 1 });
assert.deepEqual(await recover(user.openid, {}), []);
assert.deepEqual(providerCalls, []);
assert.equal(order.state, 0);
assert.equal(user.rebornGiftCount, 3);
});
for (const itemid of ['starter_pack', 'starter_pack_2', 'starter_pack_3', 'starter_pack_4', 'starter_pack_5']) {
test(`login recovers ${itemid} without order offer or cycle metadata`, async () => {
const { user, order, recover } = fixture(itemid);
const pending = await recover(user.openid, {});
assert.equal(user.starter_packState, 1); assert.equal(user.pay_user, true); assert.equal(order.state, 1);
assert.equal(pending[0].itemid, itemid); assert.equal(pending[0].starterPackOffer, undefined);
assert.equal(pending[0].starterPackBuyRound, undefined); assert.equal(pending[0].starterPackExpiresAt, undefined);
assert.deepEqual(await recover(user.openid, {}), []);
});
}
test('login payment closes the current offer without matching an order round', async () => {
const { user, order, recover } = fixture('starter_pack_3', { starterPackBuyRound: 2, starterPackExpiresAt: 1000 });
Object.assign(user, { starterPackRound: 3, starter_pack: 2000, starterPackOffer: { version: 3, round: 3 } });
assert.equal((await recover(user.openid, {})).length, 1);
assert.equal(user.starter_packState, 1); assert.equal(user.pay_user, true); assert.equal(order.state, 1);
});
test('login closes a matching migrated legacy cycle for an older order without an offer snapshot', async () => {
const { user, order, recover } = fixture('starter_pack', { time: Date.now() - 10000 });
Object.assign(user, { starter_pack: Date.now() + 10000, starterPackRound: 2, starterPackOffer: { legacy: true } });
assert.equal((await recover(user.openid, {})).length, 1);
assert.equal(order.state, 1); assert.equal(user.starter_packState, 1);
assert.equal(order.starterPackOffer, undefined);
});
test('login keeps unpaid lucky orders available past the old three-hour cleanup', async () => {
for (const itemid of ['starter_pack', 'starter_pack_5']) {
const { user, order, recover } = fixture(itemid, { time: Date.now() - 4 * 3600000 }, 'NOTPAY');
assert.deepEqual(await recover(user.openid, {}), []); assert.equal(order.state, 0);
}
});