sendou.ink/app/features/user-card/UserCardRepository.server.test.ts
2026-07-28 09:37:56 +03:00

288 lines
7.7 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "vitest";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { db } from "~/db/sql";
import { dbReset, withNoUser, withUserId } from "~/utils/Test";
import * as UserCardRepository from "./UserCardRepository.server";
import type { UserCardData } from "./user-card-types";
let owner: { id: number };
let other: { id: number };
const insertVerifiedXp = async (
userId: number,
power: number,
region: "WEST" | "JPN" = "WEST",
) => {
const player = await db
.insertInto("SplatoonPlayer")
.values({ splId: `spl-${userId}`, userId })
.returning("id")
.executeTakeFirstOrThrow();
await db
.insertInto("XRankPlacement")
.values({
playerId: player.id,
weaponSplId: 0,
badges: "[]",
bannerSplId: 1,
mode: "SZ",
month: 1,
year: 2024,
name: "Test Player",
nameDiscriminator: "0000",
power,
rank: 1,
region,
title: "Test",
})
.execute();
};
const findXpStat = (card: UserCardData | undefined) =>
card?.stats.find((stat) => stat.type === "XP");
describe("UserCardRepository.findAllByUserIds", () => {
beforeEach(async () => {
[owner, other] = await UserFactory.createMany(2);
});
afterEach(async () => {
await dbReset();
});
it("returns an empty map when given no user ids", async () => {
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({
userIds: [],
}),
);
expect(userCards.size).toBe(0);
});
it("keys cards by user id and builds the stats array from db fields", async () => {
const plusMember = await UserFactory.create(null, { plusTier: 2 });
await db
.updateTable("User")
.set({ div: "1" })
.where("id", "=", plusMember.id)
.execute();
await insertVerifiedXp(plusMember.id, 2500);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({
userIds: [plusMember.id, other.id],
}),
);
expect(userCards.size).toBe(2);
const card = userCards.get(plusMember.id);
expect(card?.id).toBe(plusMember.id);
expect(card?.freeAgentPostId).toBeNull();
const statTypes = card?.stats.map((stat) => stat.type) ?? [];
expect(statTypes).toContain("XP");
expect(statTypes).toContain("DIV");
expect(statTypes).toContain("PLUS");
expect(findXpStat(card)).toMatchObject({
type: "XP",
values: [{ isVerified: true, region: "WEST", points: 2500 }],
});
expect(card?.stats.find((stat) => stat.type === "DIV")).toMatchObject({
type: "DIV",
value: "1",
});
expect(card?.stats.find((stat) => stat.type === "PLUS")).toMatchObject({
type: "PLUS",
value: 2,
});
// user 2 has none of the optional fields -> no stats
expect(userCards.get(other.id)?.stats).toHaveLength(0);
});
it("surfaces self-reported peak XP only when it beats the verified XP", async () => {
await insertVerifiedXp(owner.id, 2500);
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: null,
unverifiedPeakXP: { overall: 2600, takoroka: null, tentatek: 2600 },
hiddenCardStats: [],
}),
);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({ userIds: [owner.id] }),
);
expect(findXpStat(userCards.get(owner.id))).toMatchObject({
type: "XP",
values: [
{ isVerified: false, region: "WEST", points: 2600 },
{ isVerified: true, region: "WEST", points: 2500 },
],
});
});
it("ignores self-reported peak XP that does not beat the verified XP", async () => {
await insertVerifiedXp(owner.id, 2500);
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: null,
unverifiedPeakXP: { overall: 2400, takoroka: null, tentatek: 2400 },
hiddenCardStats: [],
}),
);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({ userIds: [owner.id] }),
);
expect(findXpStat(userCards.get(owner.id))).toMatchObject({
type: "XP",
values: [{ isVerified: true, region: "WEST", points: 2500 }],
});
});
it("ignores self-reported peak XP more than 200 above the verified XP", async () => {
await insertVerifiedXp(owner.id, 2500);
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: null,
unverifiedPeakXP: { overall: 2800, takoroka: null, tentatek: 2800 },
hiddenCardStats: [],
}),
);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({ userIds: [owner.id] }),
);
expect(findXpStat(userCards.get(owner.id))).toMatchObject({
type: "XP",
values: [{ isVerified: true, region: "WEST", points: 2500 }],
});
});
it("ignores self-reported peak XP when there is no verified XP", async () => {
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: null,
unverifiedPeakXP: { overall: 3000, takoroka: 3000, tentatek: null },
hiddenCardStats: [],
}),
);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({ userIds: [owner.id] }),
);
expect(findXpStat(userCards.get(owner.id))).toBeUndefined();
});
it("persists edited card fields and surfaces hidden stats", async () => {
const plusMember = await UserFactory.create(null, { plusTier: 2 });
await insertVerifiedXp(plusMember.id, 2500);
await withUserId(plusMember.id, () =>
UserCardRepository.updateOwnCard({
shortBio: "hello",
bannerPresetImg: "#ff4655",
bannerImgId: null,
unverifiedPeakXP: null,
hiddenCardStats: ["XP"],
}),
);
const { userCards } = await withUserId(plusMember.id, () =>
UserCardRepository.findAllByUserIds({
userIds: [plusMember.id],
}),
);
const card = userCards.get(plusMember.id);
expect(card?.shortBio).toBe("hello");
expect(card?.banner).toMatchObject({ type: "COLOR", hexCode: "#ff4655" });
// the hidden stat is filtered out of `stats` at query time
expect(findXpStat(card)).toBeUndefined();
expect(card?.stats.find((stat) => stat.type === "PLUS")).toMatchObject({
type: "PLUS",
value: 2,
});
const extras = await UserCardRepository.findCardEditExtrasByUserId(
plusMember.id,
);
expect(extras.hiddenCardStats).toEqual(["XP"]);
});
it("keeps hidden stats in `stats` when includeHiddenStats is set", async () => {
await insertVerifiedXp(owner.id, 2500);
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: null,
unverifiedPeakXP: null,
hiddenCardStats: ["XP"],
}),
);
const { userCards } = await withUserId(owner.id, () =>
UserCardRepository.findAllByUserIds({
userIds: [owner.id],
includeHiddenStats: true,
}),
);
const card = userCards.get(owner.id);
expect(findXpStat(card)).toMatchObject({
type: "XP",
values: [{ isVerified: true, region: "WEST", points: 2500 }],
});
});
it("produces a URL banner when an uploaded banner image is set", async () => {
const image = await db
.insertInto("UnvalidatedUserSubmittedImage")
.values({
url: "banner.webp",
submitterUserId: owner.id,
validatedAt: 1,
})
.returning("id")
.executeTakeFirstOrThrow();
await withUserId(owner.id, () =>
UserCardRepository.updateOwnCard({
shortBio: null,
bannerPresetImg: null,
bannerImgId: image.id,
unverifiedPeakXP: null,
hiddenCardStats: [],
}),
);
const { userCards } = await withUserId(owner.id, () =>
UserCardRepository.findAllByUserIds({
userIds: [owner.id],
}),
);
const banner = userCards.get(owner.id)?.banner;
expect(banner?.type).toBe("URL");
expect(banner).toHaveProperty("url");
});
});