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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"lint:fix": "yarn lint --fix",
"package": "webpack --mode production --devtool hidden-source-map",
"package:prerelease": "npx vsce package --pre-release",
"pretest": "tsc -p . --outDir out && yarn run build && yarn run lint",
"pretest": "tsc -p . --outDir out && tsc -p test --outDir out && yarn run build && yarn run lint",
"test": "vitest",
"test:ci": "CI=true yarn test",
"test:integration": "vscode-test",
Expand Down
File renamed without changes.
File renamed without changes.
34 changes: 19 additions & 15 deletions test/unit/core/cliManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
MockProgressReporter,
MockUserInteraction,
} from "../../mocks/testHelpers";
import { expectPathsEqual } from "../../utils/platform";

vi.mock("os");
vi.mock("axios");
Expand Down Expand Up @@ -213,7 +214,7 @@ describe("CliManager", () => {
it("accepts valid semver versions", async () => {
withExistingBinary(TEST_VERSION);
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
});
});

Expand All @@ -226,7 +227,7 @@ describe("CliManager", () => {
it("reuses matching binary without downloading", async () => {
withExistingBinary(TEST_VERSION);
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).not.toHaveBeenCalled();
// Verify binary still exists
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
Expand All @@ -236,7 +237,7 @@ describe("CliManager", () => {
withExistingBinary("1.0.0");
withSuccessfulDownload();
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).toHaveBeenCalled();
// Verify new binary exists
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
Expand All @@ -249,7 +250,7 @@ describe("CliManager", () => {
mockConfig.set("coder.enableDownloads", false);
withExistingBinary("1.0.0");
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).not.toHaveBeenCalled();
// Should still have the old version
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
Expand All @@ -262,7 +263,7 @@ describe("CliManager", () => {
withCorruptedBinary();
withSuccessfulDownload();
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).toHaveBeenCalled();
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
expect(memfs.readFileSync(BINARY_PATH).toString()).toBe(
Expand All @@ -276,7 +277,7 @@ describe("CliManager", () => {

withSuccessfulDownload();
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).toHaveBeenCalled();

// Verify directory was created and binary exists
Expand Down Expand Up @@ -392,7 +393,7 @@ describe("CliManager", () => {
withExistingBinary("1.0.0");
withHttpResponse(304);
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
// No change
expect(memfs.readFileSync(BINARY_PATH).toString()).toBe(
mockBinaryContent("1.0.0"),
Expand Down Expand Up @@ -460,7 +461,7 @@ describe("CliManager", () => {
it("handles missing content-length", async () => {
withSuccessfulDownload({ headers: {} });
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
});
});
Expand Down Expand Up @@ -494,7 +495,7 @@ describe("CliManager", () => {
withSuccessfulDownload();
withSignatureResponses([200]);
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(pgp.verifySignature).toHaveBeenCalled();
const sigFile = expectFileInDir(BINARY_DIR, ".asc");
expect(sigFile).toBeDefined();
Expand All @@ -505,7 +506,7 @@ describe("CliManager", () => {
withSignatureResponses([404, 200]);
mockUI.setResponse("Signature not found", "Download signature");
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(mockAxios.get).toHaveBeenCalledTimes(3);
const sigFile = expectFileInDir(BINARY_DIR, ".asc");
expect(sigFile).toBeDefined();
Expand All @@ -519,7 +520,7 @@ describe("CliManager", () => {
);
mockUI.setResponse("Signature does not match", "Run anyway");
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(memfs.existsSync(BINARY_PATH)).toBe(true);
});

Expand All @@ -539,7 +540,7 @@ describe("CliManager", () => {
mockConfig.set("coder.disableSignatureVerification", true);
withSuccessfulDownload();
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(pgp.verifySignature).not.toHaveBeenCalled();
const files = readdir(BINARY_DIR);
expect(files.find((file) => file.includes(".asc"))).toBeUndefined();
Expand All @@ -553,7 +554,7 @@ describe("CliManager", () => {
withHttpResponse(status);
mockUI.setResponse(message, "Run without verification");
const result = await manager.fetchBinary(mockApi, "test");
expect(result).toBe(BINARY_PATH);
expectPathsEqual(result, BINARY_PATH);
expect(pgp.verifySignature).not.toHaveBeenCalled();
});

Expand Down Expand Up @@ -615,13 +616,16 @@ describe("CliManager", () => {

withSuccessfulDownload();
const result = await manager.fetchBinary(mockApi, "test label");
expect(result).toBe(`${pathWithSpaces}/test label/bin/${BINARY_NAME}`);
expectPathsEqual(
result,
`${pathWithSpaces}/test label/bin/${BINARY_NAME}`,
);
});

it("handles empty deployment label", async () => {
withExistingBinary(TEST_VERSION, "/path/base/bin");
const result = await manager.fetchBinary(mockApi, "");
expect(result).toBe(path.join(BASE_PATH, "bin", BINARY_NAME));
expectPathsEqual(result, path.join(BASE_PATH, "bin", BINARY_NAME));
});
});

Expand Down
14 changes: 10 additions & 4 deletions test/unit/core/cliUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { beforeAll, describe, expect, it } from "vitest";
import * as cliUtils from "@/core/cliUtils";

import { getFixturePath } from "../../utils/fixtures";
import { isWindows } from "../../utils/platform";

describe("CliUtils", () => {
const tmp = path.join(os.tmpdir(), "vscode-coder-tests");
Expand All @@ -28,12 +29,14 @@ describe("CliUtils", () => {
expect((await cliUtils.stat(binPath))?.size).toBe(4);
});

// TODO: CI only runs on Linux but we should run it on Windows too.
it("version", async () => {
it.skipIf(isWindows())("version", async () => {
const binPath = path.join(tmp, "version");
await expect(cliUtils.version(binPath)).rejects.toThrow("ENOENT");

const binTmpl = await fs.readFile(getFixturePath("bin.bash"), "utf8");
const binTmpl = await fs.readFile(
getFixturePath("scripts", "bin.bash"),
"utf8",
);
await fs.writeFile(binPath, binTmpl.replace("$ECHO", "hello"));
await expect(cliUtils.version(binPath)).rejects.toThrow("EACCES");

Expand All @@ -56,7 +59,10 @@ describe("CliUtils", () => {
);
expect(await cliUtils.version(binPath)).toBe("v0.0.0");

const oldTmpl = await fs.readFile(getFixturePath("bin.old.bash"), "utf8");
const oldTmpl = await fs.readFile(
getFixturePath("scripts", "bin.old.bash"),
"utf8",
);
const old = (stderr: string, stdout: string): string => {
return oldTmpl.replace("$STDERR", stderr).replace("$STDOUT", stdout);
};
Expand Down
28 changes: 18 additions & 10 deletions test/unit/core/pathResolver.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as path from "path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeEach, describe, it, vi } from "vitest";

import { PathResolver } from "@/core/pathResolver";

import { MockConfigurationProvider } from "../../mocks/testHelpers";
import { expectPathsEqual } from "../../utils/platform";

describe("PathResolver", () => {
const basePath =
Expand All @@ -19,32 +20,36 @@ describe("PathResolver", () => {
});

it("should use base path for empty labels", () => {
expect(pathResolver.getGlobalConfigDir("")).toBe(basePath);
expect(pathResolver.getSessionTokenPath("")).toBe(
expectPathsEqual(pathResolver.getGlobalConfigDir(""), basePath);
expectPathsEqual(
pathResolver.getSessionTokenPath(""),
path.join(basePath, "session"),
);
expect(pathResolver.getUrlPath("")).toBe(path.join(basePath, "url"));
expectPathsEqual(pathResolver.getUrlPath(""), path.join(basePath, "url"));
});

describe("getBinaryCachePath", () => {
it("should use custom binary destination when configured", () => {
mockConfig.set("coder.binaryDestination", "/custom/binary/path");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
"/custom/binary/path",
);
});

it("should use default path when custom destination is empty or whitespace", () => {
vi.stubEnv("CODER_BINARY_DESTINATION", " ");
mockConfig.set("coder.binaryDestination", " ");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
path.join(basePath, "deployment", "bin"),
);
});

it("should normalize custom paths", () => {
mockConfig.set("coder.binaryDestination", "/custom/../binary/./path");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
"/binary/path",
);
});
Expand All @@ -53,19 +58,22 @@ describe("PathResolver", () => {
// Use the global storage when the environment variable and setting are unset/blank
vi.stubEnv("CODER_BINARY_DESTINATION", "");
mockConfig.set("coder.binaryDestination", "");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
path.join(basePath, "deployment", "bin"),
);

// Test environment variable takes precedence over global storage
vi.stubEnv("CODER_BINARY_DESTINATION", " /env/binary/path ");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
"/env/binary/path",
);

// Test setting takes precedence over environment variable
mockConfig.set("coder.binaryDestination", " /setting/path ");
expect(pathResolver.getBinaryCachePath("deployment")).toBe(
expectPathsEqual(
pathResolver.getBinaryCachePath("deployment"),
"/setting/path",
);
});
Expand Down
13 changes: 11 additions & 2 deletions test/unit/globalFlags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { type WorkspaceConfiguration } from "vscode";

import { getGlobalFlags } from "@/globalFlags";

import { isWindows } from "../utils/platform";

describe("Global flags suite", () => {
it("should return global-config and header args when no global flags configured", () => {
const config = {
Expand Down Expand Up @@ -53,10 +55,11 @@ describe("Global flags suite", () => {
});

it("should not filter header-command flags, header args appended at end", () => {
const headerCommand = "echo test";
const config = {
get: (key: string) => {
if (key === "coder.headerCommand") {
return "echo test";
return headerCommand;
}
if (key === "coder.globalFlags") {
return ["-v", "--header-command custom", "--no-feature-warning"];
Expand All @@ -73,7 +76,13 @@ describe("Global flags suite", () => {
"--global-config",
'"/config/dir"',
"--header-command",
"'echo test'",
quoteCommand(headerCommand),
]);
});
});

function quoteCommand(value: string): string {
// Used to escape environment variables in commands. See `getHeaderArgs` in src/headers.ts
const quote = isWindows() ? '"' : "'";
return `${quote}${value}${quote}`;
}
Loading