Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions generated/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -127754,6 +127754,7 @@ type utility_practice_sessions {
"""An object relationship"""
match: matches
match_id: uuid
notify_when_ready: Boolean!

"""An object relationship"""
playbook: utility_playbooks
Expand Down Expand Up @@ -127892,6 +127893,7 @@ input utility_practice_sessions_bool_exp {
map_name: String_comparison_exp
match: matches_bool_exp
match_id: uuid_comparison_exp
notify_when_ready: Boolean_comparison_exp
playbook: utility_playbooks_bool_exp
playbook_id: uuid_comparison_exp
region: String_comparison_exp
Expand Down Expand Up @@ -127958,6 +127960,7 @@ input utility_practice_sessions_insert_input {
map_name: String
match: matches_obj_rel_insert_input
match_id: uuid
notify_when_ready: Boolean
playbook: utility_playbooks_obj_rel_insert_input
playbook_id: uuid
region: String
Expand Down Expand Up @@ -128133,6 +128136,7 @@ input utility_practice_sessions_order_by {
map_name: order_by
match: matches_order_by
match_id: order_by
notify_when_ready: order_by
playbook: utility_playbooks_order_by
playbook_id: order_by
region: order_by
Expand Down Expand Up @@ -128199,6 +128203,9 @@ enum utility_practice_sessions_select_column {
"""column name"""
match_id

"""column name"""
notify_when_ready

"""column name"""
playbook_id

Expand All @@ -128224,6 +128231,9 @@ enum utility_practice_sessions_select_column_utility_practice_sessions_aggregate

"""column name"""
is_render

"""column name"""
notify_when_ready
}

"""
Expand All @@ -128235,6 +128245,9 @@ enum utility_practice_sessions_select_column_utility_practice_sessions_aggregate

"""column name"""
is_render

"""column name"""
notify_when_ready
}

"""
Expand All @@ -128257,6 +128270,7 @@ input utility_practice_sessions_set_input {
map_changing_at: timestamptz
map_name: String
match_id: uuid
notify_when_ready: Boolean
playbook_id: uuid
region: String
status: e_utility_practice_statuses_enum
Expand Down Expand Up @@ -128329,6 +128343,7 @@ input utility_practice_sessions_stream_cursor_value_input {
map_changing_at: timestamptz
map_name: String
match_id: uuid
notify_when_ready: Boolean
playbook_id: uuid
region: String
status: e_utility_practice_statuses_enum
Expand Down Expand Up @@ -128400,6 +128415,9 @@ enum utility_practice_sessions_update_column {
"""column name"""
match_id

"""column name"""
notify_when_ready

"""column name"""
playbook_id

Expand Down
28 changes: 17 additions & 11 deletions generated/schema.ts

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions generated/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141996,6 +141996,9 @@ export default {
"match_id": [
6365
],
"notify_when_ready": [
6
],
"playbook": [
6250
],
Expand Down Expand Up @@ -142300,6 +142303,9 @@ export default {
"match_id": [
6367
],
"notify_when_ready": [
7
],
"playbook": [
6254
],
Expand Down Expand Up @@ -142398,6 +142404,9 @@ export default {
"match_id": [
6365
],
"notify_when_ready": [
6
],
"playbook": [
6261
],
Expand Down Expand Up @@ -142774,6 +142783,9 @@ export default {
"match_id": [
3558
],
"notify_when_ready": [
3558
],
"playbook": [
6263
],
Expand Down Expand Up @@ -142859,6 +142871,9 @@ export default {
"match_id": [
6365
],
"notify_when_ready": [
6
],
"playbook_id": [
6365
],
Expand Down Expand Up @@ -142986,6 +143001,9 @@ export default {
"match_id": [
6365
],
"notify_when_ready": [
6
],
"playbook_id": [
6365
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE "public"."utility_practice_sessions"
DROP COLUMN IF EXISTS "notify_when_ready";
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Who is still owed a "your server is up".
--
-- The practice bar in the top nav says it already, on every page, for as long
-- as the session lasts -- so for somebody who pressed Start and stayed there,
-- a bell row says the same thing a moment later and says it worse. The
-- exception is a player who was turned away for want of a server: they queued,
-- and the whole point of a queue is that you stop watching it. Only their
-- session carries this.
ALTER TABLE "public"."utility_practice_sessions"
ADD COLUMN IF NOT EXISTS "notify_when_ready" boolean NOT NULL DEFAULT false;
128 changes: 128 additions & 0 deletions src/matches/match-assistant/match-assistant.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ describe("MatchAssistantService", () => {
getDelayed: jest.Mock;
};

// Every "we could not get you a server" write goes out as one conditional
// update_matches, so the assertions read the statement rather than a status
// setter that is no longer how the assignment path says it.
function waitingForServerWrites() {
return hasura.mutation.mock.calls
.map(([mutation]) => mutation?.update_matches)
.filter(
(update) => update?.__args?._set?.status === "WaitingForServer",
);
}

beforeEach(() => {
hasura = {
query: jest.fn(),
Expand Down Expand Up @@ -202,6 +213,123 @@ describe("MatchAssistantService", () => {
expect(startMatch).not.toHaveBeenCalled();
});

// A dedicated server runs the match plugin; the utility practice plugin ships
// only in the on-demand image. Falling back to one gave a practice session a
// connect string -- so the website read "ready to join" -- for a box that can
// never answer GET /utility/session, which is what turns the session Ready.
it("never falls back to a dedicated server for a practice match", async () => {
hasura.query.mockResolvedValue({
matches_by_pk: {
id: "match-1",
region: "USE",
source: "practice",
options: {
prefer_dedicated_server: false,
},
},
});

jest.spyOn(service as any, "assignOnDemandServer").mockResolvedValue(false);
const assignDedicated = jest
.spyOn(service as any, "assignDedicatedServer")
.mockResolvedValue(true);

await expect(service.assignServer("match-1")).resolves.toBeUndefined();

expect(assignDedicated).not.toHaveBeenCalled();
expect(waitingForServerWrites()).toHaveLength(1);
});

// countFreeOnDemandServers answers zero for "everything is busy" and for
// "there is no node here", and only the second is hopeless -- the practice
// start reads this to decide whether queuing could ever help.
describe("hasOnDemandNodes", () => {
it("is false when no node in the region can take a pod", async () => {
hasura.query.mockResolvedValue({ game_server_nodes: [] });

expect(await service.hasOnDemandNodes("USE")).toBe(false);
});

it("asks only about the region it was given", async () => {
hasura.query.mockResolvedValue({ game_server_nodes: [{ id: "node-1" }] });

expect(await service.hasOnDemandNodes("USE")).toBe(true);

const [[query]] = hasura.query.mock.calls;

expect(query.game_server_nodes.__args.where.region).toEqual({
_eq: "USE",
});
expect(query.game_server_nodes.__args.where.status).toEqual({
_eq: "Online",
});
});

it("asks about the whole install when given no region", async () => {
hasura.query.mockResolvedValue({ game_server_nodes: [{ id: "node-1" }] });

await service.hasOnDemandNodes();

const [[query]] = hasura.query.mock.calls;

expect(query.game_server_nodes.__args.where.region).toBeUndefined();
});
});

// The boot attempt outlives the host pressing Stop. Writing the status back
// without a condition moved the already Canceled match to WaitingForServer,
// and with the practice session already Ended nothing was left to move it
// again -- so the row sat there forever.
it("cannot move a finished match back to waiting for a server", async () => {
hasura.query.mockResolvedValue({
matches_by_pk: {
id: "match-1",
region: "USE",
source: "practice",
options: {
prefer_dedicated_server: false,
},
},
});

jest.spyOn(service as any, "assignOnDemandServer").mockResolvedValue(false);

await service.assignServer("match-1");

const [write] = waitingForServerWrites();

expect(write.__args.where.status._nin).toEqual(
expect.arrayContaining(["Canceled", "Finished", "WaitingForServer"]),
);
});

// The same guard on the other side of the branch: prefer_dedicated_server is
// an option a practice match never sets, but nothing stops it being set.
it("ignores prefer_dedicated_server on a practice match", async () => {
hasura.query.mockResolvedValue({
matches_by_pk: {
id: "match-1",
region: "USE",
source: "practice",
options: {
prefer_dedicated_server: true,
},
},
});

const assignOnDemand = jest
.spyOn(service as any, "assignOnDemandServer")
.mockResolvedValue(true);
const assignDedicated = jest
.spyOn(service as any, "assignDedicatedServer")
.mockResolvedValue(true);

await expect(service.assignServer("match-1")).resolves.toBeUndefined();

expect(assignOnDemand).toHaveBeenCalled();
expect(assignDedicated).not.toHaveBeenCalled();
});

it("schedules the next on-demand server boot check after 15 seconds", async () => {
await service.delayCheckOnDemandServer("match-1");

Expand Down
Loading
Loading