Fix SQ weapon etc. cache not purged on match profile change

This commit is contained in:
Kalle
2026-09-20 08:02:40 +03:00
parent 43778b8582
commit 592bb33cdf
4 changed files with 117 additions and 7 deletions

View File

@@ -8,19 +8,19 @@ import {
} from "../q-constants";
import { refreshSendouQInstance, SendouQ } from "./SendouQ.server";
/** Cancels every pending challenge involving the user's active full group. Partial groups merge rather than start a match on accept, so their preferences are not locked in yet. */
/** Cancels every pending challenge involving the user's active full group, returning whether any were, which also means the instance was refreshed. Partial groups merge rather than start a match on accept, so their preferences are not locked in yet. */
export async function cancelActiveGroupLikes(userId: number) {
const ownGroup = SendouQ.findOwnGroup(userId);
if (!ownGroup) return;
if (ownGroup.status !== "ACTIVE" || ownGroup.matchId) return;
if (ownGroup.members.length !== FULL_GROUP_SIZE) return;
if (!ownGroup) return false;
if (ownGroup.status !== "ACTIVE" || ownGroup.matchId) return false;
if (ownGroup.members.length !== FULL_GROUP_SIZE) return false;
const likes = await SQGroupRepository.findAllLikesByGroupId(ownGroup.id);
const affectedGroupIds = R.unique([
...likes.given.map((like) => like.groupId),
...likes.received.map((like) => like.groupId),
]);
if (affectedGroupIds.length === 0) return;
if (affectedGroupIds.length === 0) return false;
await SQGroupRepository.deleteAllLikesByGroupId(ownGroup.id);
@@ -40,4 +40,6 @@ export async function cancelActiveGroupLikes(userId: number) {
) ?? [],
),
);
return true;
}

View File

@@ -0,0 +1,76 @@
import { beforeEach, describe, expect, test, vi } from "vitest";
import * as SQGroupFactory from "~/db/seed/factories/SQGroupFactory";
import * as UserFactory from "~/db/seed/factories/UserFactory";
import * as MatchProfileRepository from "~/features/match-profile/MatchProfileRepository.server";
import {
refreshSendouQInstance,
SendouQ,
} from "~/features/sendouq/core/SendouQ.server";
import type { MainWeaponId } from "~/modules/in-game-lists/types";
import { wrappedAction } from "~/utils/Test";
import type { settingsActionSchema } from "../settings-schemas.server";
import { action } from "./settings.server";
vi.mock("~/features/chat/ChatSystemMessage.server", () => ({
send: vi.fn(),
notifyStatusChanged: vi.fn(),
notifyNotificationsChanged: vi.fn(),
}));
const OLD_WEAPON_ID: MainWeaponId = 0;
const NEW_WEAPON_ID: MainWeaponId = 40;
const settingsAction = wrappedAction<typeof settingsActionSchema>({
action,
isJsonSubmission: true,
});
const users = UserFactory.pool();
const actorId = () => users.id(1);
const updateWeaponPool = (weaponId: MainWeaponId) =>
settingsAction(
{
_action: "UPDATE_MATCH_PROFILE",
mapModePreferences: { modes: [], pool: [] },
weaponPool: [{ id: weaponId, isFavorite: false }],
vc: "NO",
languages: [],
noScreen: false,
},
{ user: actorId() },
);
const ownWeaponsInQueue = () =>
SendouQ.findOwnGroup(actorId())
?.members.find((member) => member.id === actorId())
?.weapons?.map((weapon) => weapon.weaponSplId);
describe("settings action: UPDATE_MATCH_PROFILE", () => {
beforeEach(async () => {
await users.create(1, null, {
matchProfile: { weaponPool: [{ id: OLD_WEAPON_ID, isFavorite: false }] },
});
});
test("a new weapon pool is shown to the queue right away", async () => {
await SQGroupFactory.create({ memberUserIds: [actorId()] });
await refreshSendouQInstance();
await updateWeaponPool(NEW_WEAPON_ID);
expect(ownWeaponsInQueue()).toEqual([NEW_WEAPON_ID]);
});
test("updates the weapon pool of a user who is not queueing", async () => {
await updateWeaponPool(NEW_WEAPON_ID);
const weaponPool = await MatchProfileRepository.findWeaponPoolByUserId(
actorId(),
);
expect(weaponPool.map((weapon) => weapon.weaponSplId)).toEqual([
NEW_WEAPON_ID,
]);
});
});

View File

@@ -1,7 +1,16 @@
import type { ActionFunctionArgs } from "react-router";
import { requireUser } from "~/features/auth/core/user.server";
import * as ChatSystemMessage from "~/features/chat/ChatSystemMessage.server";
import * as MatchProfileRepository from "~/features/match-profile/MatchProfileRepository.server";
import { cancelActiveGroupLikes } from "~/features/sendouq/core/likes.server";
import {
refreshSendouQInstance,
SendouQ,
} from "~/features/sendouq/core/SendouQ.server";
import {
SENDOUQ_LOOKING_CHANNEL,
sqGroupChannel,
} from "~/features/sendouq/q-constants";
import * as UserRepository from "~/features/user-page/UserRepository.server";
import { parseFormData } from "~/form/parse.server";
import { isSupporter } from "~/modules/permissions/utils";
@@ -78,8 +87,13 @@ export const action = async ({ request }: ActionFunctionArgs) => {
});
// challenges are based on the preferences shown at the time, so changing them undoes pending ones
if (mapModePreferencesChanged || noScreenChanged) {
await cancelActiveGroupLikes(user.id);
const likesCancelled =
mapModePreferencesChanged || noScreenChanged
? await cancelActiveGroupLikes(user.id)
: false;
if (!likesCancelled) {
await showMatchProfileToOthers(user.id);
}
break;
}
@@ -90,3 +104,16 @@ export const action = async ({ request }: ActionFunctionArgs) => {
return null;
};
/** SendouQ pages serve group members from the in-memory instance, so a match profile change stays invisible to everyone else till it is rebuilt. */
async function showMatchProfileToOthers(userId: number) {
const ownGroup = SendouQ.findOwnGroup(userId);
if (!ownGroup) return;
await refreshSendouQInstance();
ChatSystemMessage.send([
{ channel: sqGroupChannel(ownGroup.id) },
{ channel: SENDOUQ_LOOKING_CHANNEL },
]);
}

View File

@@ -0,0 +1,5 @@
---
navItem: sendouq
type: bug
---
Changes to your SendouQ info (weapon pool, languages, voice chat) now show up to other players right away instead of after a delay