mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-27 06:15:24 +00:00
169 lines
7.1 KiB
TypeScript
169 lines
7.1 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { createElement, useState } from "react";
|
|
import TestRenderer, { act } from "react-test-renderer";
|
|
import type { Environment, EnvironmentResourcesSettings } from "@qltysh/fabro-api-client";
|
|
|
|
import { setupReactTestEnv } from "../lib/test-utils";
|
|
import {
|
|
EMPTY_ENVIRONMENT_FORM,
|
|
EnvironmentFormFields,
|
|
createRequestFromForm,
|
|
environmentToFormValues,
|
|
isEnvironmentFormValid,
|
|
replaceRequestFromForm,
|
|
type EnvironmentFormValues,
|
|
} from "./environment-form";
|
|
|
|
function form(overrides: Partial<EnvironmentFormValues>): EnvironmentFormValues {
|
|
return { ...EMPTY_ENVIRONMENT_FORM, id: "docker", ...overrides };
|
|
}
|
|
|
|
describe("environment image source", () => {
|
|
test("image source requires a non-empty image reference", () => {
|
|
expect(isEnvironmentFormValid(form({ imageSource: "image", dockerRef: "" }))).toBe(false);
|
|
expect(
|
|
isEnvironmentFormValid(form({ imageSource: "image", dockerRef: "ubuntu:24.04" })),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("dockerfile source requires non-empty Dockerfile contents", () => {
|
|
expect(isEnvironmentFormValid(form({ imageSource: "dockerfile", dockerfile: "" }))).toBe(false);
|
|
expect(
|
|
isEnvironmentFormValid(form({ imageSource: "dockerfile", dockerfile: "FROM ubuntu" })),
|
|
).toBe(true);
|
|
});
|
|
|
|
test("an empty Dockerfile does not satisfy the image-reference source", () => {
|
|
expect(
|
|
isEnvironmentFormValid(form({ imageSource: "image", dockerRef: "", dockerfile: "FROM x" })),
|
|
).toBe(false);
|
|
});
|
|
|
|
test("image source sends only the docker reference", () => {
|
|
const request = createRequestFromForm(
|
|
form({ imageSource: "image", dockerRef: "ubuntu:24.04", dockerfile: "FROM leftover" }),
|
|
);
|
|
expect(request.image.docker).toBe("ubuntu:24.04");
|
|
expect(request.image.dockerfile).toBeNull();
|
|
});
|
|
|
|
test("dockerfile source sends only the inline Dockerfile", () => {
|
|
const request = createRequestFromForm(
|
|
form({ imageSource: "dockerfile", dockerRef: "leftover", dockerfile: "FROM ubuntu" }),
|
|
);
|
|
expect(request.image.docker).toBeNull();
|
|
expect(request.image.dockerfile?.value).toBe("FROM ubuntu");
|
|
});
|
|
});
|
|
|
|
function environment(resources: EnvironmentResourcesSettings): Environment {
|
|
return {
|
|
id: "roundtrip",
|
|
revision: "revision-1",
|
|
provider: "docker",
|
|
image: { docker: "ubuntu:24.04", dockerfile: null },
|
|
resources,
|
|
network: { mode: "allow_all", allow: [] },
|
|
lifecycle: { preserve: false, stop_on_terminal: true, auto_stop: null },
|
|
labels: { owner: "api", purpose: "roundtrip" },
|
|
env: { TZ: "UTC" },
|
|
};
|
|
}
|
|
|
|
describe("environment resource round trips", () => {
|
|
const cases: EnvironmentResourcesSettings[] = [
|
|
// API response for 512MiB; regression originally reported in #952.
|
|
{ cpu: null, memory: "536870912B", disk: null },
|
|
{ cpu: 4, memory: "4GB", disk: "8GB" },
|
|
{ cpu: null, memory: null, disk: null },
|
|
{ cpu: 32, memory: "64GB", disk: "2TB" },
|
|
{ cpu: 1, memory: "512MiB", disk: "1GiB" },
|
|
{ cpu: 2, memory: "1.5GB", disk: "512MB" },
|
|
{ cpu: 2, memory: "1B", disk: "1TiB" },
|
|
];
|
|
|
|
for (const resources of cases) {
|
|
test(`no-op save preserves ${JSON.stringify(resources)}`, () => {
|
|
const original = environment(resources);
|
|
const { id, revision, ...settings } = original;
|
|
expect(replaceRequestFromForm(environmentToFormValues(original))).toEqual(settings);
|
|
});
|
|
|
|
test(`unrelated edits preserve ${JSON.stringify(resources)} and labels`, () => {
|
|
const original = environment(resources);
|
|
const values = environmentToFormValues(original);
|
|
const saved = replaceRequestFromForm({
|
|
...values,
|
|
dockerRef: "ubuntu:26.04",
|
|
preserve: true,
|
|
autoStop: "30m",
|
|
envVars: [...values.envVars, { key: "EDITOR", value: "vi" }],
|
|
});
|
|
expect(saved.resources).toEqual(resources);
|
|
expect(saved.labels).toEqual(original.labels);
|
|
expect(saved.image.docker).toBe("ubuntu:26.04");
|
|
expect(saved.lifecycle).toEqual({ preserve: true, stop_on_terminal: true, auto_stop: "30m" });
|
|
expect(saved.env).toEqual({ TZ: "UTC", EDITOR: "vi" });
|
|
});
|
|
}
|
|
|
|
test("editing one resource preserves the other resources", () => {
|
|
const values = environmentToFormValues(environment(cases[0]));
|
|
expect(replaceRequestFromForm({ ...values, cpu: 6 }).resources).toEqual({
|
|
...cases[0], cpu: 6,
|
|
});
|
|
expect(replaceRequestFromForm({ ...values, memory: "6GB" }).resources).toEqual({
|
|
...cases[0], memory: "6GB",
|
|
});
|
|
expect(replaceRequestFromForm({ ...values, disk: "12GB" }).resources).toEqual({
|
|
...cases[0], disk: "12GB",
|
|
});
|
|
});
|
|
|
|
test("new environments keep the existing resource defaults", () => {
|
|
expect(createRequestFromForm(form({ dockerRef: "ubuntu:24.04" })).resources)
|
|
.toEqual({ cpu: 4, memory: "8GB", disk: "16GB" });
|
|
});
|
|
});
|
|
|
|
test("sliders preserve loaded resources until that specific slider changes", () => {
|
|
const cleanup = setupReactTestEnv();
|
|
let renderer: TestRenderer.ReactTestRenderer | undefined;
|
|
let saved: ReturnType<typeof replaceRequestFromForm> | undefined;
|
|
function Editor() {
|
|
const [values, setValues] = useState(() => environmentToFormValues(environment({
|
|
cpu: null, memory: "536870912B", disk: null,
|
|
})));
|
|
saved = replaceRequestFromForm(values);
|
|
return createElement(EnvironmentFormFields, { values, onChange: setValues, lockId: true });
|
|
}
|
|
try {
|
|
act(() => { renderer = TestRenderer.create(createElement(Editor)); });
|
|
const input = (label: string) => renderer!.root.findByProps({ "aria-label": label });
|
|
for (const [label, min, max] of [["CPU", 1, 8], ["Memory", 1, 16], ["Disk", 1, 20]] as const) {
|
|
expect(input(label).props.type).toBe("range");
|
|
expect(input(label).props.min).toBe(min);
|
|
expect(input(label).props.max).toBe(max);
|
|
expect(input(label).props.step).toBe(1);
|
|
}
|
|
expect(input("CPU").props["aria-valuetext"]).toBe("Provider default");
|
|
expect(input("Memory").props.value).toBe(1);
|
|
expect(input("Memory").props["aria-valuetext"]).toBe("536870912B");
|
|
expect(input("Disk").props["aria-valuetext"]).toBe("Provider default");
|
|
act(() => { input("Image reference").props.onChange({ target: { value: "alpine:latest" } }); });
|
|
expect(saved!.resources).toEqual({ cpu: null, memory: "536870912B", disk: null });
|
|
expect(saved!.labels).toEqual({ owner: "api", purpose: "roundtrip" });
|
|
act(() => { input("CPU").props.onChange({ target: { value: "6" } }); });
|
|
expect(saved!.resources).toEqual({ cpu: 6, memory: "536870912B", disk: null });
|
|
// Selecting the displayed fallback is an explicit edit, even when the
|
|
// numeric position matches the initial projection of the loaded value.
|
|
act(() => { input("Memory").props.onChange({ target: { value: "2" } }); });
|
|
act(() => { input("Memory").props.onChange({ target: { value: "1" } }); });
|
|
expect(saved!.resources).toEqual({ cpu: 6, memory: "1GB", disk: null });
|
|
act(() => { input("Disk").props.onChange({ target: { value: "12" } }); });
|
|
expect(saved!.resources).toEqual({ cpu: 6, memory: "1GB", disk: "12GB" });
|
|
} finally {
|
|
act(() => { renderer?.unmount(); });
|
|
cleanup();
|
|
}
|
|
});
|