This commit is contained in:
Kalle 2026-07-28 09:37:56 +03:00
parent 5e6d292d7a
commit 369d81eefe
8 changed files with 185 additions and 223 deletions

View File

@ -5,10 +5,11 @@ type RequiredArgs<Args, Defaults> = Omit<Args, keyof Defaults>;
type CreateArgs<Args, Defaults> = RequiredArgs<Args, Defaults> & Partial<Args>;
/** `null` says "all defaults", for when only `options` is of interest. */
type CreateParams<Args, Defaults, Options> = [
keyof RequiredArgs<Args, Defaults>,
] extends [never]
? [overrides?: Partial<Args>, options?: Options]
? [overrides?: Partial<Args> | null, options?: Options]
: [overrides: CreateArgs<Args, Defaults>, options?: Options];
type CreateManyParams<Args, Defaults, Options> = [
@ -16,7 +17,7 @@ type CreateManyParams<Args, Defaults, Options> = [
] extends [never]
? [
count: number,
overrides?: ManyOverrides<Args, Defaults>,
overrides?: ManyOverrides<Args, Defaults> | null,
options?: Options,
]
: [
@ -135,7 +136,7 @@ export function resetFactories() {
}
function overridesAt<Args, Defaults>(
overrides: ManyOverrides<Args, Defaults> | undefined,
overrides: ManyOverrides<Args, Defaults> | null | undefined,
index: number,
): Partial<Args> {
if (!overrides) return {};

View File

@ -1,4 +1,9 @@
import { add } from "date-fns";
import { db } from "~/db/sql";
import type { Tables } from "~/db/tables";
import * as AdminRepository from "~/features/admin/AdminRepository.server";
import { ADMIN_ID } from "~/features/admin/admin-constants";
import * as BuildRepository from "~/features/builds/BuildRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import invariant from "~/utils/invariant";
import { REGULAR_USER_TEST_ID } from "../constants";
@ -8,6 +13,24 @@ import { pinUserId } from "../core/pinUserId";
type UpsertArgs = Parameters<typeof UserRepository.upsert>[0];
type Role = "ARTIST" | "VIDEO_ADDER" | "TOURNAMENT_ORGANIZER" | "API_ACCESSER";
type Options = {
plusTier?: Tables["PlusTier"]["tier"];
/** Patron who started supporting now and has a year left of it. */
patronTier?: NonNullable<Tables["User"]["patronTier"]>;
roles?: Array<Role>;
};
const PATRONAGE_LENGTH = { years: 1 };
const GRANT_ROLE: Record<Role, (userId: number) => Promise<unknown>> = {
ARTIST: AdminRepository.makeArtistByUserId,
VIDEO_ADDER: AdminRepository.makeVideoAdderByUserId,
TOURNAMENT_ORGANIZER: AdminRepository.makeTournamentOrganizerByUserId,
API_ACCESSER: AdminRepository.makeApiAccesserByUserId,
};
/** Creates users. Columns outside `UserRepository.upsert` (profile fields, patron
* status, plus tier) are set by the repository function that owns them. */
export const { create, createMany } = defineFactory({
@ -21,6 +44,7 @@ export const { create, createMany } = defineFactory({
bsky: null,
}),
insert: UserRepository.upsert,
applyOptions: (user, options: Options) => grantPrivileges(user.id, options),
});
/**
@ -29,22 +53,41 @@ export const { create, createMany } = defineFactory({
*
* Has to be created before any other user, see {@link pinUserId}.
*/
export async function createAdmin(overrides?: Partial<UpsertArgs>) {
export async function createAdmin(
overrides?: Partial<UpsertArgs> | null,
options?: Options,
) {
const user = await create(overrides);
const id = await pinUserId(user.id, ADMIN_ID);
return { id: await pinUserId(user.id, ADMIN_ID) };
// after pinning, so that rows keyed by the user id don't point at the old one
if (options) {
await grantPrivileges(id, options);
}
return { id };
}
/**
* Creates the user that `wrappedAction({ user: "regular" })` submits as. Has no
* permissions of any kind; use for the "somebody else" side of a permission test.
* permissions of any kind unless `options` gives it some; use for the "somebody
* else" side of a permission test.
*
* Has to be created before any user without a pinned id, see {@link pinUserId}.
*/
export async function createRegular(overrides?: Partial<UpsertArgs>) {
export async function createRegular(
overrides?: Partial<UpsertArgs> | null,
options?: Options,
) {
const user = await create(overrides);
const id = await pinUserId(user.id, REGULAR_USER_TEST_ID);
return { id: await pinUserId(user.id, REGULAR_USER_TEST_ID) };
// after pinning, so that rows keyed by the user id don't point at the old one
if (options) {
await grantPrivileges(id, options);
}
return { id };
}
/**
@ -87,3 +130,43 @@ export function pool() {
},
};
}
async function grantPrivileges(
userId: number,
{ plusTier, patronTier, roles }: Options,
) {
if (typeof plusTier === "number") {
await setPlusTier(userId, plusTier);
}
if (typeof patronTier === "number") {
await AdminRepository.forcePatron({
id: userId,
patronTier,
patronStartedAt: new Date(),
patronExpiresAt: add(new Date(), PATRONAGE_LENGTH),
});
}
for (const role of roles ?? []) {
await GRANT_ROLE[role](userId);
}
}
async function setPlusTier(userId: number, plusTier: number) {
// `replacePlusTiers` replaces every row, being the monthly recount of who is in
// what tier, so the tiers granted before this one are read back and sent along
const others = await db
.selectFrom("PlusTier")
.select(["userId", "tier"])
.where("userId", "!=", userId)
.execute();
await AdminRepository.replacePlusTiers([
...others.map((other) => ({ userId: other.userId, plusTier: other.tier })),
{ userId, plusTier },
]);
// xxx: should be in replacePlusTiers etc.?
await BuildRepository.recalculateAllSortValues(userId);
}

View File

@ -1,7 +1,5 @@
import { add } from "date-fns";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import { afterEach, describe, expect, test } from "vitest";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import * as AdminRepository from "~/features/admin/AdminRepository.server";
import * as TournamentOrganizationRepository from "~/features/tournament-organization/TournamentOrganizationRepository.server";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { dbReset } from "~/utils/Test";
@ -9,23 +7,17 @@ import * as ApiRepository from "../ApiRepository.server";
import { checkUserHasApiAccess } from "./perms";
describe("Permission logic consistency between findAllApiTokens and checkUserHasApiAccess", () => {
const users = UserFactory.pool();
beforeEach(async () => {
await users.create(4);
});
afterEach(async () => {
await dbReset();
});
test("both functions grant access for isApiAccesser flag", async () => {
await AdminRepository.makeApiAccesserByUserId(users.id(1));
const { id } = await UserFactory.create(null, { roles: ["API_ACCESSER"] });
await ApiRepository.generateToken(users.id(1), "read");
await ApiRepository.generateToken(id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(1));
const user = await UserRepository.findLeanById(id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(1);
@ -33,12 +25,15 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions grant access for isTournamentOrganizer flag", async () => {
await AdminRepository.makeTournamentOrganizerByUserId(users.id(1));
const { id } = await UserFactory.create(
{},
{ roles: ["TOURNAMENT_ORGANIZER"] },
);
await ApiRepository.generateToken(users.id(1), "read");
await ApiRepository.generateToken(id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(1));
const user = await UserRepository.findLeanById(id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(1);
@ -46,17 +41,12 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions grant access for patronTier >= 2", async () => {
await AdminRepository.forcePatron({
id: users.id(1),
patronTier: 2,
patronStartedAt: new Date(),
patronExpiresAt: add(new Date(), { months: 3 }),
});
const { id } = await UserFactory.create(null, { patronTier: 2 });
await ApiRepository.generateToken(users.id(1), "read");
await ApiRepository.generateToken(id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(1));
const user = await UserRepository.findLeanById(id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(1);
@ -64,17 +54,12 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions deny access for patronTier < 2", async () => {
await AdminRepository.forcePatron({
id: users.id(1),
patronTier: 1,
patronStartedAt: new Date(),
patronExpiresAt: add(new Date(), { months: 3 }),
});
const { id } = await UserFactory.create(null, { patronTier: 1 });
await ApiRepository.generateToken(users.id(1), "read");
await ApiRepository.generateToken(id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(1));
const user = await UserRepository.findLeanById(id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(0);
@ -82,8 +67,10 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions grant access for ADMIN/ORGANIZER/STREAMER of established org", async () => {
const [owner, admin, organizer, streamer] = await UserFactory.createMany(4);
const org = await TournamentOrganizationRepository.insert({
ownerId: users.id(1),
ownerId: owner.id,
name: "Test Org",
});
@ -91,14 +78,13 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
const orgData = await TournamentOrganizationRepository.findBySlug(org.slug);
for (const role of ["ADMIN", "ORGANIZER", "STREAMER"] as const) {
const userId =
role === "ADMIN"
? users.id(2)
: role === "ORGANIZER"
? users.id(3)
: users.id(4);
const membersByRole = [
{ role: "ADMIN", userId: admin.id },
{ role: "ORGANIZER", userId: organizer.id },
{ role: "STREAMER", userId: streamer.id },
] as const;
for (const { role, userId } of membersByRole) {
await TournamentOrganizationRepository.update({
id: org.id,
name: orgData!.name,
@ -121,8 +107,10 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions deny access for MEMBER of established org", async () => {
const [owner, member] = await UserFactory.createMany(2);
const org = await TournamentOrganizationRepository.insert({
ownerId: users.id(1),
ownerId: owner.id,
name: "Test Org",
});
@ -134,15 +122,15 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
name: orgData!.name,
description: orgData!.description,
socials: orgData!.socials,
members: [{ userId: users.id(2), role: "MEMBER", roleDisplayName: null }],
members: [{ userId: member.id, role: "MEMBER", roleDisplayName: null }],
series: [],
badges: [],
});
await ApiRepository.generateToken(users.id(2), "read");
await ApiRepository.generateToken(member.id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(2));
const user = await UserRepository.findLeanById(member.id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(0);
@ -150,8 +138,10 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
});
test("both functions deny access for ADMIN of non-established org", async () => {
const [owner, member] = await UserFactory.createMany(2);
const org = await TournamentOrganizationRepository.insert({
ownerId: users.id(1),
ownerId: owner.id,
name: "Test Org",
});
@ -161,15 +151,15 @@ describe("Permission logic consistency between findAllApiTokens and checkUserHas
name: orgData!.name,
description: orgData!.description,
socials: orgData!.socials,
members: [{ userId: users.id(2), role: "ADMIN", roleDisplayName: null }],
members: [{ userId: member.id, role: "ADMIN", roleDisplayName: null }],
series: [],
badges: [],
});
await ApiRepository.generateToken(users.id(2), "read");
await ApiRepository.generateToken(member.id, "read");
const tokens = await ApiRepository.findAllApiTokens();
const user = await UserRepository.findLeanById(users.id(2));
const user = await UserRepository.findLeanById(member.id);
const hasAccess = await checkUserHasApiAccess(user!);
expect(tokens).toHaveLength(0);

View File

@ -6,7 +6,6 @@ import type {
MainWeaponId,
} from "~/modules/in-game-lists/types";
import { dbReset } from "~/utils/Test";
import * as AdminRepository from "../admin/AdminRepository.server";
import * as BuildRepository from "./BuildRepository.server";
let owner: { id: number };
@ -218,22 +217,20 @@ describe("BuildRepository.insert — computeBuildData", () => {
});
test("uses owner's PlusTier (tier 2 → sortValue = 5)", async () => {
await AdminRepository.replacePlusTiers([
{ userId: owner.id, plusTier: 2 },
]);
const plusOwner = await UserFactory.create(null, { plusTier: 2 });
await BuildRepository.insert(baseArgs());
await BuildRepository.insert(baseArgs({ ownerId: plusOwner.id }));
const [weapon] = await buildWeaponsByBuildId(await onlyBuildId());
expect(weapon.sortValue).toBe(5);
});
test("is null for private builds regardless of tier", async () => {
await AdminRepository.replacePlusTiers([
{ userId: owner.id, plusTier: 1 },
]);
const plusOwner = await UserFactory.create(null, { plusTier: 1 });
await BuildRepository.insert(baseArgs({ isPrivate: 1 }));
await BuildRepository.insert(
baseArgs({ ownerId: plusOwner.id, isPrivate: 1 }),
);
const [weapon] = await buildWeaponsByBuildId(await onlyBuildId());
expect(weapon.sortValue).toBeNull();
@ -258,13 +255,11 @@ describe("BuildRepository.insert — computeBuildData", () => {
});
test("combines top500 with the owner's PlusTier", async () => {
await AdminRepository.replacePlusTiers([
{ userId: owner.id, plusTier: 1 },
]);
const playerId = await insertSplatoonPlayer(owner.id, "owner-spl-id");
const plusOwner = await UserFactory.create(null, { plusTier: 1 });
const playerId = await insertSplatoonPlayer(plusOwner.id, "owner-spl-id");
await insertXRankPlacement(playerId, SPLATTERSHOT, 1);
await BuildRepository.insert(baseArgs());
await BuildRepository.insert(baseArgs({ ownerId: plusOwner.id }));
const [weapon] = await buildWeaponsByBuildId(await onlyBuildId());
expect(weapon.sortValue).toBe(2);

View File

@ -2,7 +2,6 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { REGULAR_USER_TEST_ID } from "~/db/seed/constants";
import * as ImageFactory from "~/db/seed/factories/ImageFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import * as AdminRepository from "~/features/admin/AdminRepository.server";
import * as ImageRepository from "~/features/img-upload/ImageRepository.server";
import * as TeamRepository from "~/features/team/TeamRepository.server";
import invariant from "~/utils/invariant";
@ -51,17 +50,10 @@ const VALID_CUSTOM_THEME = {
const expectedStoredTheme = () =>
JSON.parse(JSON.stringify(clampThemeToGamut(VALID_CUSTOM_THEME)));
const makeUserPatron = () =>
AdminRepository.forcePatron({
id: REGULAR_USER_TEST_ID,
patronTier: 2,
patronStartedAt: new Date(),
patronExpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
});
describe("team page editing", () => {
beforeEach(async () => {
await UserFactory.createRegular();
// a patron because setting a custom theme is a patron only feature
await UserFactory.createRegular(null, { patronTier: 2 });
await createTeamAction({ name: "Team 1" }, { user: "regular" });
});
afterEach(async () => {
@ -69,8 +61,6 @@ describe("team page editing", () => {
});
it("sets a custom theme via UPDATE_CUSTOM_THEME", async () => {
await makeUserPatron();
const response = await editTeamProfileAction(
{
_action: "UPDATE_CUSTOM_THEME",
@ -86,8 +76,6 @@ describe("team page editing", () => {
});
it("clears a custom theme via UPDATE_CUSTOM_THEME with null", async () => {
await makeUserPatron();
await editTeamProfileAction(
{
_action: "UPDATE_CUSTOM_THEME",
@ -111,8 +99,6 @@ describe("team page editing", () => {
});
it("prevents setting an invalid custom theme", async () => {
await makeUserPatron();
const response = await editTeamProfileAction(
{
_action: "UPDATE_CUSTOM_THEME",
@ -128,8 +114,6 @@ describe("team page editing", () => {
});
it("preserves an existing custom theme when editing the team profile", async () => {
await makeUserPatron();
await editTeamProfileAction(
{
_action: "UPDATE_CUSTOM_THEME",

View File

@ -1,7 +1,6 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { REGULAR_USER_TEST_ID } from "~/db/seed/constants";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import * as AdminRepository from "~/features/admin/AdminRepository.server";
import invariant from "~/utils/invariant";
import {
assertResponseErrored,
@ -175,18 +174,17 @@ describe("Secondary teams", () => {
assertResponseErrored(response);
});
});
const makeUserPatron = () =>
AdminRepository.forcePatron({
id: REGULAR_USER_TEST_ID,
patronTier: 2,
patronStartedAt: new Date(),
patronExpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24),
});
describe("Secondary teams as patron", () => {
beforeEach(async () => {
await UserFactory.createRegular(null, { patronTier: 2 });
});
afterEach(async () => {
await dbReset();
});
it("creates more than 2 teams as patron", async () => {
await makeUserPatron();
await createTeamAction({ name: "Team 1" }, { user: "regular" });
await createTeamAction({ name: "Team 2" }, { user: "regular" });
await createTeamAction({ name: "Team 3" }, { user: "regular" });

View File

@ -61,27 +61,24 @@ describe("UserCardRepository.findAllByUserIds", () => {
});
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", "=", owner.id)
.where("id", "=", plusMember.id)
.execute();
await db
.insertInto("PlusTier")
.values({ userId: owner.id, tier: 2 })
.execute();
await insertVerifiedXp(owner.id, 2500);
await insertVerifiedXp(plusMember.id, 2500);
const { userCards } = await withNoUser(() =>
UserCardRepository.findAllByUserIds({
userIds: [owner.id, other.id],
userIds: [plusMember.id, other.id],
}),
);
expect(userCards.size).toBe(2);
const card = userCards.get(owner.id);
expect(card?.id).toBe(owner.id);
const card = userCards.get(plusMember.id);
expect(card?.id).toBe(plusMember.id);
expect(card?.freeAgentPostId).toBeNull();
const statTypes = card?.stats.map((stat) => stat.type) ?? [];
@ -194,13 +191,10 @@ describe("UserCardRepository.findAllByUserIds", () => {
});
it("persists edited card fields and surfaces hidden stats", async () => {
await db
.insertInto("PlusTier")
.values({ userId: owner.id, tier: 2 })
.execute();
await insertVerifiedXp(owner.id, 2500);
const plusMember = await UserFactory.create(null, { plusTier: 2 });
await insertVerifiedXp(plusMember.id, 2500);
await withUserId(owner.id, () =>
await withUserId(plusMember.id, () =>
UserCardRepository.updateOwnCard({
shortBio: "hello",
bannerPresetImg: "#ff4655",
@ -210,12 +204,12 @@ describe("UserCardRepository.findAllByUserIds", () => {
}),
);
const { userCards } = await withUserId(owner.id, () =>
const { userCards } = await withUserId(plusMember.id, () =>
UserCardRepository.findAllByUserIds({
userIds: [owner.id],
userIds: [plusMember.id],
}),
);
const card = userCards.get(owner.id);
const card = userCards.get(plusMember.id);
expect(card?.shortBio).toBe("hello");
expect(card?.banner).toMatchObject({ type: "COLOR", hexCode: "#ff4655" });
@ -227,7 +221,7 @@ describe("UserCardRepository.findAllByUserIds", () => {
});
const extras = await UserCardRepository.findCardEditExtrasByUserId(
owner.id,
plusMember.id,
);
expect(extras.hiddenCardStats).toEqual(["XP"]);
});

View File

@ -1,6 +1,6 @@
import { afterEach, describe, expect, test } from "vitest";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import { dbReset } from "~/utils/Test";
import * as AdminRepository from "../admin/AdminRepository.server";
import * as UserRepository from "./UserRepository.server";
describe("UserRepository", () => {
@ -107,19 +107,13 @@ describe("UserRepository", () => {
describe("userRoles", () => {
test("returns empty array for basic user", async () => {
await UserRepository.upsert({
discordId: "79237403620945920",
discordName: "DummyAdmin",
discordAvatar: null,
});
await UserFactory.createAdmin();
const recentDiscordId = String(
(BigInt(Date.now() - 1420070400000) << 22n) + 1n,
);
const { id } = await UserRepository.upsert({
const { id } = await UserFactory.create({
discordId: recentDiscordId,
discordName: "RegularUser",
discordAvatar: null,
});
const user = await UserRepository.findLeanById(id);
@ -128,11 +122,7 @@ describe("UserRepository", () => {
});
test("returns ADMIN and STAFF roles for admin user", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945920",
discordName: "AdminUser",
discordAvatar: null,
});
const { id } = await UserFactory.createAdmin();
const user = await UserRepository.findLeanById(id);
@ -141,22 +131,7 @@ describe("UserRepository", () => {
});
test("returns MINOR_SUPPORT role for patron tier 1", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "PatronUser",
discordAvatar: null,
});
const now = new Date();
const oneYearFromNow = new Date(
now.getTime() + 365 * 24 * 60 * 60 * 1000,
);
await AdminRepository.forcePatron({
id,
patronTier: 1,
patronStartedAt: now,
patronExpiresAt: oneYearFromNow,
});
const { id } = await UserFactory.create(null, { patronTier: 1 });
const user = await UserRepository.findLeanById(id);
@ -165,22 +140,7 @@ describe("UserRepository", () => {
});
test("returns SUPPORTER, MINOR_SUPPORT, TOURNAMENT_ADDER, CALENDAR_EVENT_ADDER, and API_ACCESSER roles for patron tier 2", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "SupporterUser",
discordAvatar: null,
});
const now = new Date();
const oneYearFromNow = new Date(
now.getTime() + 365 * 24 * 60 * 60 * 1000,
);
await AdminRepository.forcePatron({
id,
patronTier: 2,
patronStartedAt: now,
patronExpiresAt: oneYearFromNow,
});
const { id } = await UserFactory.create(null, { patronTier: 2 });
const user = await UserRepository.findLeanById(id);
@ -192,13 +152,7 @@ describe("UserRepository", () => {
});
test("returns PLUS_SERVER_MEMBER role for plus tier user", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "PlusUser",
discordAvatar: null,
});
await AdminRepository.replacePlusTiers([{ userId: id, plusTier: 1 }]);
const { id } = await UserFactory.create(null, { plusTier: 1 });
const user = await UserRepository.findLeanById(id);
@ -206,13 +160,7 @@ describe("UserRepository", () => {
});
test("returns ARTIST role for artist user", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "ArtistUser",
discordAvatar: null,
});
await AdminRepository.makeArtistByUserId(id);
const { id } = await UserFactory.create(null, { roles: ["ARTIST"] });
const user = await UserRepository.findLeanById(id);
@ -220,13 +168,7 @@ describe("UserRepository", () => {
});
test("returns VIDEO_ADDER role for video adder user", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "VideoAdderUser",
discordAvatar: null,
});
await AdminRepository.makeVideoAdderByUserId(id);
const { id } = await UserFactory.create(null, { roles: ["VIDEO_ADDER"] });
const user = await UserRepository.findLeanById(id);
@ -234,13 +176,10 @@ describe("UserRepository", () => {
});
test("returns TOURNAMENT_ADDER and API_ACCESSER roles for tournament organizer", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "OrganizerUser",
discordAvatar: null,
});
await AdminRepository.makeTournamentOrganizerByUserId(id);
const { id } = await UserFactory.create(
{},
{ roles: ["TOURNAMENT_ORGANIZER"] },
);
const user = await UserRepository.findLeanById(id);
@ -249,14 +188,10 @@ describe("UserRepository", () => {
});
test("returns API_ACCESSER role for api accesser user", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945921",
discordName: "ApiUser",
discordAvatar: null,
const { id } = await UserFactory.create(null, {
roles: ["API_ACCESSER"],
});
await AdminRepository.makeApiAccesserByUserId(id);
const user = await UserRepository.findLeanById(id);
expect(user?.roles).toContain("API_ACCESSER");
@ -264,11 +199,7 @@ describe("UserRepository", () => {
test("returns CALENDAR_EVENT_ADDER role for aged discord account", async () => {
const agedDiscordId = "79237403620945921";
const { id } = await UserRepository.upsert({
discordId: agedDiscordId,
discordName: "AgedUser",
discordAvatar: null,
});
const { id } = await UserFactory.create({ discordId: agedDiscordId });
const user = await UserRepository.findLeanById(id);
@ -280,10 +211,8 @@ describe("UserRepository", () => {
(BigInt(Date.now() - 1420070400000) << 22n) + 1n,
);
const { id } = await UserRepository.upsert({
const { id } = await UserFactory.create({
discordId: recentDiscordId,
discordName: "NewUser",
discordAvatar: null,
});
const user = await UserRepository.findLeanById(id);
@ -292,26 +221,14 @@ describe("UserRepository", () => {
});
test("returns multiple roles for user with multiple privileges", async () => {
const { id } = await UserRepository.upsert({
discordId: "79237403620945920",
discordName: "MultiRoleUser",
discordAvatar: null,
});
const now = new Date();
const oneYearFromNow = new Date(
now.getTime() + 365 * 24 * 60 * 60 * 1000,
const { id } = await UserFactory.create(
{},
{
patronTier: 2,
plusTier: 2,
roles: ["ARTIST", "VIDEO_ADDER"],
},
);
await AdminRepository.forcePatron({
id,
patronTier: 2,
patronStartedAt: now,
patronExpiresAt: oneYearFromNow,
});
await AdminRepository.makeArtistByUserId(id);
await AdminRepository.makeVideoAdderByUserId(id);
await AdminRepository.replacePlusTiers([{ userId: id, plusTier: 2 }]);
const user = await UserRepository.findLeanById(id);