This pull request enhances the VS Code Web module by improving how machine settings are handled and merged, updating documentation to clarify the settings behavior, and adding robust automated tests for the new functionality. The most significant changes are grouped below. **Machine Settings Handling and Merging:** * Introduced a new `merge_settings` function in `run.sh` that merges provided settings with any existing machine settings using `jq` or `python3` if available, falling back gracefully if neither is present. Settings are now passed as base64-encoded JSON to avoid quoting issues. [[1]](diffhunk://#diff-c6d09ac3d801a2417c0e3cf8c2cd0f093ba2cf245bad8c213f70115c75276323R7-R54) [[2]](diffhunk://#diff-c6d09ac3d801a2417c0e3cf8c2cd0f093ba2cf245bad8c213f70115c75276323L31-R76) [[3]](diffhunk://#diff-0c7f0791e2c2556eb4ed7666ac44534ea3ff5c7f652e01716e5d7b5c31180d92L180-R184) [[4]](diffhunk://#diff-0c7f0791e2c2556eb4ed7666ac44534ea3ff5c7f652e01716e5d7b5c31180d92R170-R173) * Updated the `settings` variable in `main.tf` to clarify that it applies to VS Code Web's Machine settings and will be merged with any existing settings on startup. **Documentation Improvements:** * Updated the README to clarify that settings are merged with existing machine settings, not simply overwritten, and added a note about the requirements (`jq` or `python3`) and limitations regarding persistence of user settings. [[1]](diffhunk://#diff-24e2e305e46a08f8a30243bdc916241586e4561d97861b4397b14e871f9f085dL54-R56) [[2]](diffhunk://#diff-24e2e305e46a08f8a30243bdc916241586e4561d97861b4397b14e871f9f085dR72-R73) **Automated Testing:** * Expanded `main.test.ts` to include integration tests that verify settings file creation and merging behavior inside a container, as well as improved error handling for invalid configuration combinations. These changes collectively make machine settings management more robust, user-friendly, and well-documented.
299 lines
8.8 KiB
TypeScript
299 lines
8.8 KiB
TypeScript
import {
|
|
describe,
|
|
expect,
|
|
it,
|
|
beforeAll,
|
|
afterEach,
|
|
setDefaultTimeout,
|
|
} from "bun:test";
|
|
import {
|
|
runTerraformApply,
|
|
runTerraformInit,
|
|
runContainer,
|
|
execContainer,
|
|
removeContainer,
|
|
findResourceInstance,
|
|
} from "~test";
|
|
|
|
// Set timeout to 2 minutes for tests that install packages
|
|
setDefaultTimeout(2 * 60 * 1000);
|
|
|
|
let cleanupContainers: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
for (const id of cleanupContainers) {
|
|
try {
|
|
await removeContainer(id);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
cleanupContainers = [];
|
|
});
|
|
|
|
describe("vscode-web", async () => {
|
|
beforeAll(async () => {
|
|
await runTerraformInit(import.meta.dir);
|
|
});
|
|
|
|
it("accept_license should be set to true", async () => {
|
|
try {
|
|
await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: false,
|
|
});
|
|
throw new Error("Expected terraform apply to fail");
|
|
} catch (ex) {
|
|
expect((ex as Error).message).toContain("Invalid value for variable");
|
|
}
|
|
});
|
|
|
|
it("use_cached and offline can not be used together", async () => {
|
|
try {
|
|
await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
use_cached: true,
|
|
offline: true,
|
|
});
|
|
throw new Error("Expected terraform apply to fail");
|
|
} catch (ex) {
|
|
expect((ex as Error).message).toContain(
|
|
"Offline and Use Cached can not be used together",
|
|
);
|
|
}
|
|
});
|
|
|
|
it("offline and extensions can not be used together", async () => {
|
|
try {
|
|
await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
offline: true,
|
|
extensions: '["ms-python.python"]',
|
|
});
|
|
throw new Error("Expected terraform apply to fail");
|
|
} catch (ex) {
|
|
expect((ex as Error).message).toContain(
|
|
"Offline mode does not allow extensions to be installed",
|
|
);
|
|
}
|
|
});
|
|
|
|
it("creates settings file with correct content", async () => {
|
|
const state = await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
use_cached: true,
|
|
settings: '{"editor.fontSize": 14}',
|
|
});
|
|
|
|
const containerId = await runContainer("ubuntu:22.04");
|
|
cleanupContainers.push(containerId);
|
|
|
|
// Create a mock code-server CLI that the script expects
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /tmp/vscode-web/bin && cat > /tmp/vscode-web/bin/code-server << 'MOCKEOF'
|
|
#!/bin/bash
|
|
echo "Mock code-server running"
|
|
exit 0
|
|
MOCKEOF
|
|
chmod +x /tmp/vscode-web/bin/code-server`,
|
|
]);
|
|
|
|
const script = findResourceInstance(state, "coder_script");
|
|
|
|
const scriptResult = await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
script.script,
|
|
]);
|
|
expect(scriptResult.exitCode).toBe(0);
|
|
|
|
// Check that settings file was created
|
|
const settingsResult = await execContainer(containerId, [
|
|
"cat",
|
|
"/root/.vscode-server/data/Machine/settings.json",
|
|
]);
|
|
|
|
expect(settingsResult.exitCode).toBe(0);
|
|
expect(settingsResult.stdout).toContain("editor.fontSize");
|
|
expect(settingsResult.stdout).toContain("14");
|
|
});
|
|
|
|
it("merges settings with existing settings file", async () => {
|
|
const state = await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
use_cached: true,
|
|
settings: '{"new.setting": "new_value"}',
|
|
});
|
|
|
|
const containerId = await runContainer("ubuntu:22.04");
|
|
cleanupContainers.push(containerId);
|
|
|
|
// Install jq and create mock code-server CLI
|
|
await execContainer(containerId, ["apt-get", "update", "-qq"]);
|
|
await execContainer(containerId, ["apt-get", "install", "-y", "-qq", "jq"]);
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /tmp/vscode-web/bin && cat > /tmp/vscode-web/bin/code-server << 'MOCKEOF'
|
|
#!/bin/bash
|
|
echo "Mock code-server running"
|
|
exit 0
|
|
MOCKEOF
|
|
chmod +x /tmp/vscode-web/bin/code-server`,
|
|
]);
|
|
|
|
// Pre-create an existing settings file
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /root/.vscode-server/data/Machine && echo '{"existing.setting": "existing_value"}' > /root/.vscode-server/data/Machine/settings.json`,
|
|
]);
|
|
|
|
const script = findResourceInstance(state, "coder_script");
|
|
|
|
const scriptResult = await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
script.script,
|
|
]);
|
|
expect(scriptResult.exitCode).toBe(0);
|
|
|
|
// Check that settings were merged (both existing and new should be present)
|
|
const settingsResult = await execContainer(containerId, [
|
|
"cat",
|
|
"/root/.vscode-server/data/Machine/settings.json",
|
|
]);
|
|
|
|
expect(settingsResult.exitCode).toBe(0);
|
|
// Should contain both existing and new settings
|
|
expect(settingsResult.stdout).toContain("existing.setting");
|
|
expect(settingsResult.stdout).toContain("existing_value");
|
|
expect(settingsResult.stdout).toContain("new.setting");
|
|
expect(settingsResult.stdout).toContain("new_value");
|
|
});
|
|
|
|
it("merges settings using python3 fallback when jq unavailable", async () => {
|
|
const state = await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
use_cached: true,
|
|
settings: '{"new.setting": "new_value"}',
|
|
});
|
|
|
|
const containerId = await runContainer("ubuntu:22.04");
|
|
cleanupContainers.push(containerId);
|
|
|
|
// Install python3 (ubuntu:22.04 doesn't have it by default)
|
|
await execContainer(containerId, ["apt-get", "update", "-qq"]);
|
|
await execContainer(containerId, [
|
|
"apt-get",
|
|
"install",
|
|
"-y",
|
|
"-qq",
|
|
"python3",
|
|
]);
|
|
|
|
// Create mock code-server CLI (no jq installed)
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /tmp/vscode-web/bin && cat > /tmp/vscode-web/bin/code-server << 'MOCKEOF'
|
|
#!/bin/bash
|
|
echo "Mock code-server running"
|
|
exit 0
|
|
MOCKEOF
|
|
chmod +x /tmp/vscode-web/bin/code-server`,
|
|
]);
|
|
|
|
// Pre-create an existing settings file
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /root/.vscode-server/data/Machine && echo '{"existing.setting": "existing_value"}' > /root/.vscode-server/data/Machine/settings.json`,
|
|
]);
|
|
|
|
const script = findResourceInstance(state, "coder_script");
|
|
|
|
const scriptResult = await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
script.script,
|
|
]);
|
|
expect(scriptResult.exitCode).toBe(0);
|
|
|
|
// Check that settings were merged using python3 fallback
|
|
const settingsResult = await execContainer(containerId, [
|
|
"cat",
|
|
"/root/.vscode-server/data/Machine/settings.json",
|
|
]);
|
|
|
|
expect(settingsResult.exitCode).toBe(0);
|
|
// Should contain both existing and new settings
|
|
expect(settingsResult.stdout).toContain("existing.setting");
|
|
expect(settingsResult.stdout).toContain("existing_value");
|
|
expect(settingsResult.stdout).toContain("new.setting");
|
|
expect(settingsResult.stdout).toContain("new_value");
|
|
});
|
|
|
|
it("preserves existing settings when neither jq nor python3 available", async () => {
|
|
const state = await runTerraformApply(import.meta.dir, {
|
|
agent_id: "foo",
|
|
accept_license: true,
|
|
use_cached: true,
|
|
settings: '{"new.setting": "new_value"}',
|
|
});
|
|
|
|
// Use ubuntu without installing jq or python3 (neither available by default)
|
|
const containerId = await runContainer("ubuntu:22.04");
|
|
cleanupContainers.push(containerId);
|
|
|
|
// Create mock code-server CLI
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /tmp/vscode-web/bin && cat > /tmp/vscode-web/bin/code-server << 'MOCKEOF'
|
|
#!/bin/bash
|
|
echo "Mock code-server running"
|
|
exit 0
|
|
MOCKEOF
|
|
chmod +x /tmp/vscode-web/bin/code-server`,
|
|
]);
|
|
|
|
// Pre-create an existing settings file
|
|
await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
`mkdir -p /root/.vscode-server/data/Machine && echo '{"existing.setting": "existing_value"}' > /root/.vscode-server/data/Machine/settings.json`,
|
|
]);
|
|
|
|
const script = findResourceInstance(state, "coder_script");
|
|
|
|
// Run script - should warn but not fail
|
|
const scriptResult = await execContainer(containerId, [
|
|
"bash",
|
|
"-c",
|
|
script.script,
|
|
]);
|
|
expect(scriptResult.exitCode).toBe(0);
|
|
expect(scriptResult.stdout).toContain("Could not merge settings");
|
|
|
|
// Existing settings should be preserved (not overwritten)
|
|
const settingsResult = await execContainer(containerId, [
|
|
"cat",
|
|
"/root/.vscode-server/data/Machine/settings.json",
|
|
]);
|
|
|
|
expect(settingsResult.exitCode).toBe(0);
|
|
expect(settingsResult.stdout).toContain("existing.setting");
|
|
expect(settingsResult.stdout).toContain("existing_value");
|
|
expect(settingsResult.stdout).not.toContain("new.setting");
|
|
expect(settingsResult.stdout).not.toContain("new_value");
|
|
});
|
|
});
|