Skip to content
Source
Sign inSign up

versionSkew.test.ts

150 lines5.1 KB

1import { EnvironmentId } from "@t3tools/contracts";
2import type { ServerUpdateState } from "@t3tools/client-runtime/state/server";
3import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
4​
5const branding = vi.hoisted(() => ({ APP_VERSION: "0.0.34" }));
6vi.mock("./branding", () => branding);
7​
8import { APP_VERSION } from "./branding";
9import {
10 buildVersionMismatchDismissalKey,
11 dismissServerUpdateFailure,
12 dismissVersionMismatch,
13 isServerUpdateFailureDismissed,
14 isVersionMismatchDismissed,
15 manualServerUpdateCommand,
16 resolveVersionMismatch,
17} from "./versionSkew";
18​
19const MISMATCH_HINT =
20 "Version mismatch. Try syncing the client and server to the same T3 Code version.";
21​
22describe("versionSkew", () => {
23 it("updates only the proven install and safely quotes its paths", () => {
24 expect(manualServerUpdateCommand("0.0.45", { kind: "npm-global", prefix: "/opt/node" })).toBe(
25 "npm install --global --prefix '/opt/node' t3@0.0.45",
26 );
27 expect(
28 manualServerUpdateCommand("0.0.45", { kind: "npm-global", prefix: "/opt/maria's node" }),
29 ).toBe("npm install --global --prefix '/opt/maria'\\''s node' t3@0.0.45");
30 expect(
31 manualServerUpdateCommand("0.0.45", {
32 kind: "t3-launcher",
33 launcher: "t3",
34 baseDir: "/home/u/.t3",
35 }),
36 ).toBe("'t3' update --base-dir '/home/u/.t3' 0.0.45");
37 expect(
38 manualServerUpdateCommand("0.0.45", {
39 kind: "t3-launcher",
40 launcher: "/opt/maria's bin/t3",
41 baseDir: "/data/t3 home",
42 }),
43 ).toBe("'/opt/maria'\\''s bin/t3' update --base-dir '/data/t3 home' 0.0.45");
44 });
45 beforeEach(() => {
46 branding.APP_VERSION = "0.0.34";
47 });
48​
49 it("dismisses only the current failed attempt without clearing its retry state", () => {
50 const failure = {
51 status: "failed",
52 stage: "downloading",
53 fromVersion: "0.0.33",
54 targetVersion: "0.0.34",
55 message: "Download failed.",
56 } as const satisfies ServerUpdateState;
57 const retryFailure = { ...failure };
58 const otherEnvironmentFailure = { ...failure };
59​
60 dismissServerUpdateFailure(failure);
61​
62 expect(isServerUpdateFailureDismissed(failure)).toBe(true);
63 expect(failure.status).toBe("failed");
64 expect(failure.message).toBe("Download failed.");
65 expect(isServerUpdateFailureDismissed(retryFailure)).toBe(false);
66 expect(isServerUpdateFailureDismissed(otherEnvironmentFailure)).toBe(false);
67 });
68​
69 it("returns a mismatch when the server is behind the client", () => {
70 expect(resolveVersionMismatch("0.0.33")).toEqual({
71 clientVersion: "0.0.34",
72 serverVersion: "0.0.33",
73 hint: MISMATCH_HINT,
74 });
75 });
76​
77 it("does not warn when the server is ahead of the client", () => {
78 expect(resolveVersionMismatch("0.0.35")).toBeNull();
79 expect(resolveVersionMismatch("9.9.9")).toBeNull();
80 });
81​
82 it("does not warn when a nightly and a stable build share a core version", () => {
83 expect(resolveVersionMismatch("0.0.34-nightly.20260818.1124")).toBeNull();
84​
85 branding.APP_VERSION = "0.0.34-nightly.20260818.1124";
86 expect(resolveVersionMismatch("0.0.34")).toBeNull();
87 });
88​
89 it.each(["0.0.34-nightly.20260823.1124", "0.0.34-nightly.20260824.1124"])(
90 "warns when nightly server %s is behind a nightly client on the same release",
91 (serverVersion) => {
92 branding.APP_VERSION = "0.0.34-nightly.20260824.1125";
93​
94 expect(resolveVersionMismatch(serverVersion)).toEqual({
95 clientVersion: "0.0.34-nightly.20260824.1125",
96 serverVersion,
97 hint: MISMATCH_HINT,
98 });
99 },
100 );
101​
102 // regression: #7579 versions were compared as strings, not semver
103 it("treats a nightly server built past the client as ahead, not skew", () => {
104 expect(resolveVersionMismatch("0.0.35-nightly.20260818.1124")).toBeNull();
105 });
106​
107 it("still warns when a nightly client outruns the server by a release", () => {
108 branding.APP_VERSION = "0.0.35-nightly.20260818.1124";
109​
110 expect(resolveVersionMismatch("0.0.34")).toEqual({
111 clientVersion: "0.0.35-nightly.20260818.1124",
112 serverVersion: "0.0.34",
113 hint: MISMATCH_HINT,
114 });
115 });
116​
117 it("falls back to string inequality when a version is not semver", () => {
118 expect(resolveVersionMismatch("dev")).toEqual({
119 clientVersion: "0.0.34",
120 serverVersion: "dev",
121 hint: MISMATCH_HINT,
122 });
123​
124 branding.APP_VERSION = "dev";
125 expect(resolveVersionMismatch("dev")).toBeNull();
126 expect(resolveVersionMismatch("0.0.34")).toMatchObject({ serverVersion: "0.0.34" });
127 });
128​
129 it("keys dismissals by environment, client version, and server version", () => {
130 const environmentId = EnvironmentId.make("environment-dismissal");
131 const key = buildVersionMismatchDismissalKey(environmentId, {
132 clientVersion: APP_VERSION,
133 serverVersion: "9.9.9",
134 });
135​
136 expect(isVersionMismatchDismissed(key)).toBe(false);
137​
138 dismissVersionMismatch(key);
139​
140 expect(isVersionMismatchDismissed(key)).toBe(true);
141 expect(
142 isVersionMismatchDismissed(
143 buildVersionMismatchDismissalKey(environmentId, {
144 clientVersion: APP_VERSION,
145 serverVersion: "9.9.10",
146 }),
147 ),
148 ).toBe(false);
149 });
150});