Files
server/laf-cloud/tests/user-data-save.test.mjs

121 lines
4.1 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { registerHooks } from "node:module";
const state = { user: null, failUpdate: false, loseMatch: false, legacyResult: false, results: [] };
const profile = { useravatar: "2", username: "player", useravatarIcon: "1", userCat: "3" };
globalThis.__userDataSaveCloudMock = {
database() {
return {
collection() {
return {
where(query) {
return {
async getOne() {
return { data: state.user?._id === query._id ? structuredClone(state.user) : null };
},
async update(payload) {
if (state.loseMatch) state.user = null;
const matched = state.user?._id === query._id ? 1 : 0;
const updated = !state.failUpdate && matched
&& Object.entries(payload).some(([key, value]) => state.user[key] !== value) ? 1 : 0;
if (updated) Object.assign(state.user, payload);
const result = { ok: !state.failUpdate, updated, ...(!state.legacyResult ? { matched } : {}) };
state.results.push(result);
return result;
},
};
},
};
},
};
},
};
globalThis.__userDataSaveUtilsMock = {
checkToken(received, expected) { return received === expected; },
};
registerHooks({
resolve(specifier, context, nextResolve) {
if (specifier === "@lafjs/cloud") {
return { url: "data:text/javascript,export default globalThis.__userDataSaveCloudMock", shortCircuit: true };
}
if (specifier === "@/Utils") {
return { url: "data:text/javascript,export default globalThis.__userDataSaveUtilsMock", shortCircuit: true };
}
return nextResolve(specifier, context);
},
});
const { default: userData } = await import(new URL("../functions/userData.ts", import.meta.url));
function reset() {
state.user = { _id: "user-1", token: "token-1", useravatar: "0", username: "user", useravatarIcon: "0", userCat: "1" };
state.failUpdate = false;
state.loseMatch = false;
state.legacyResult = false;
state.results = [];
}
function save(extra = {}) {
return userData({ body: {
uid: "user-1", token: "token-1", action: "save", userData: JSON.stringify(profile), ...extra,
} });
}
test("saving changed and then identical profile data both succeeds", async () => {
reset();
const first = await save();
const repeated = await save();
assert.deepEqual(state.results, [
{ ok: true, matched: 1, updated: 1 },
{ ok: true, matched: 1, updated: 0 },
]);
assert.equal(first.code, 1);
assert.equal(repeated.code, 1);
assert.deepEqual(state.user, { _id: "user-1", token: "token-1", ...profile });
});
test("a missing uid is rejected without updating", async () => {
reset();
assert.equal((await save({ uid: undefined })).code, 0);
assert.equal(state.results.length, 0);
});
test("an unknown user is not mistaken for an unchanged successful save", async () => {
reset();
const before = structuredClone(state.user);
assert.equal((await save({ uid: "missing-user" })).code, 0);
assert.deepEqual(state.user, before);
});
test("losing the matching user during the update remains a failure", async () => {
reset();
state.loseMatch = true;
assert.equal((await save()).code, 0);
assert.deepEqual(state.results, [{ ok: true, matched: 0, updated: 0 }]);
});
test("an explicitly failed database update remains a failure", async () => {
reset();
state.failUpdate = true;
const before = structuredClone(state.user);
assert.equal((await save()).code, 0);
assert.deepEqual(state.results, [{ ok: false, matched: 1, updated: 0 }]);
assert.deepEqual(state.user, before);
});
test("an invalid token prevents all writes", async () => {
reset();
assert.equal((await save({ token: "wrong-token" })).code, 0);
assert.equal(state.results.length, 0);
});
test("legacy update results with only ok and updated still succeed", async () => {
reset();
state.legacyResult = true;
assert.equal((await save()).code, 1);
assert.deepEqual(state.results, [{ ok: true, updated: 1 }]);
});