2026-06-29 13:23:07 -03:00
|
|
|
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
|
2026-06-29 14:49:49 -03:00
|
|
|
import { RouterLinkStub, flushPromises, mount, shallowMount } from "@vue/test-utils";
|
2026-04-11 00:02:26 -03:00
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
import PlatformSection from "../../../frontend/src/components/PlatformSection.vue";
|
|
|
|
|
import { auth } from "../../../frontend/src/auth.js";
|
2026-04-11 00:02:26 -03:00
|
|
|
import HomeView from "../../../frontend/src/views/HomeView.vue";
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
function resetAuth() {
|
|
|
|
|
auth.ready = true;
|
|
|
|
|
auth.enabled = true;
|
|
|
|
|
auth.authenticated = false;
|
|
|
|
|
auth.username = "";
|
|
|
|
|
auth.email = "";
|
|
|
|
|
auth.emailVerified = null;
|
|
|
|
|
auth.groups = [];
|
|
|
|
|
auth.resetUrl = "https://sso.example.dev/reset";
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 00:02:26 -03:00
|
|
|
describe("HomeView", () => {
|
2026-06-29 13:23:07 -03:00
|
|
|
beforeEach(() => {
|
|
|
|
|
global.fetch = jest.fn(async (resource) => {
|
|
|
|
|
const url = typeof resource === "string" ? resource : resource?.url || "";
|
|
|
|
|
if (url.includes("/api/account/member-state")) {
|
|
|
|
|
return new Response(
|
|
|
|
|
JSON.stringify({
|
|
|
|
|
status: "ready",
|
|
|
|
|
onboarding_url: "/onboarding?code=ada~CODE",
|
|
|
|
|
dashboard_url: "/apps",
|
|
|
|
|
account_url: "/account",
|
|
|
|
|
}),
|
|
|
|
|
{ status: 200, headers: { "content-type": "application/json" } },
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return new Response("{}", { status: 200, headers: { "content-type": "application/json" } });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
resetAuth();
|
|
|
|
|
jest.restoreAllMocks();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("renders the professional homepage while logged out", () => {
|
|
|
|
|
resetAuth();
|
2026-06-29 14:49:49 -03:00
|
|
|
const wrapper = mount(HomeView, {
|
2026-04-11 00:02:26 -03:00
|
|
|
global: {
|
|
|
|
|
stubs: {
|
2026-06-29 13:23:07 -03:00
|
|
|
RouterLink: RouterLinkStub,
|
|
|
|
|
PlatformSection: true,
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
props: {
|
2026-06-29 13:23:07 -03:00
|
|
|
labStatus: {
|
|
|
|
|
connected: true,
|
|
|
|
|
atlas: { up: true, known: true },
|
|
|
|
|
active_service: { known: true, label: "Nextcloud", url: "https://cloud.example.dev", window: "15m" },
|
|
|
|
|
dedicated_hosts: {
|
|
|
|
|
known: true,
|
|
|
|
|
up: true,
|
|
|
|
|
up_count: 2,
|
|
|
|
|
total: 2,
|
|
|
|
|
hosts: [
|
|
|
|
|
{ label: "Database node", known: true, up: true },
|
|
|
|
|
{ label: "Jumphost", known: true, up: true },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
checked_at: 1000,
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
2026-06-29 13:23:07 -03:00
|
|
|
loading: false,
|
|
|
|
|
error: "",
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.text()).toContain("Brad Stein");
|
|
|
|
|
expect(wrapper.text()).toContain("DevOps Automation Engineer / Senior SDET");
|
|
|
|
|
expect(wrapper.text()).toContain("Discuss a Project");
|
|
|
|
|
expect(wrapper.text()).toContain("View Résumé");
|
|
|
|
|
expect(wrapper.text()).toContain("Explore the Live Platform");
|
|
|
|
|
expect(wrapper.text()).toContain("From 0 to 100");
|
|
|
|
|
expect(wrapper.text()).toContain("Platform Showcase");
|
|
|
|
|
expect(wrapper.text()).toContain("Atlas operator tools");
|
|
|
|
|
expect(wrapper.text()).toContain("Veles");
|
|
|
|
|
expect(wrapper.text()).not.toContain("TaskWatcher");
|
|
|
|
|
expect(wrapper.text()).toContain("family, friends, and the lucky few");
|
|
|
|
|
expect(wrapper.text()).toContain("Register");
|
|
|
|
|
expect(wrapper.text()).toContain("Request Lab Access");
|
|
|
|
|
expect(wrapper.find("[aria-label='Keycloak: Use one account to sign in across bstein.dev.']").exists()).toBe(true);
|
|
|
|
|
expect(wrapper.find("[title='Nextcloud: Keep files, photos, office docs, and personal cloud data in sync.']").exists()).toBe(true);
|
|
|
|
|
expect(wrapper.find("a[href='https://bstein.dev/ai/chat']").exists()).toBe(true);
|
|
|
|
|
expect(wrapper.find("a[href='https://bstein.dev/monero']").exists()).toBe(true);
|
|
|
|
|
expect(wrapper.find("a[href='https://chat.ai.bstein.dev']").exists()).toBe(false);
|
|
|
|
|
expect(wrapper.find("a[href='https://monero.bstein.dev:443']").exists()).toBe(false);
|
|
|
|
|
expect(wrapper.find("a[href='/ai/chat']").exists()).toBe(false);
|
|
|
|
|
expect(wrapper.find("a[href='/monero']").exists()).toBe(false);
|
|
|
|
|
expect(wrapper.text()).not.toContain("Existing registration, verification, onboarding");
|
|
|
|
|
expect(wrapper.text()).not.toContain("Looking for hosted services or an existing Titan Lab account");
|
|
|
|
|
expect(wrapper.find("a[href^='mailto:brad@bstein.dev']").exists()).toBe(true);
|
|
|
|
|
expect(wrapper.findComponent(PlatformSection).exists()).toBe(true);
|
2026-06-29 16:45:06 -03:00
|
|
|
expect(wrapper.text()).toContain("Atlas cluster");
|
|
|
|
|
expect(wrapper.text()).toContain("Most requested service");
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.text()).toContain("Nextcloud");
|
2026-06-29 16:45:06 -03:00
|
|
|
expect(wrapper.text()).toContain("Outpost hosts");
|
|
|
|
|
expect(wrapper.text()).toContain("2 of 2 online");
|
|
|
|
|
expect(wrapper.text()).toContain("Database machine");
|
|
|
|
|
expect(wrapper.text()).toContain("Jumphost");
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.text()).toContain("Responding");
|
2026-04-11 00:02:26 -03:00
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
it("shows a calm fallback when public status is unavailable", () => {
|
|
|
|
|
resetAuth();
|
2026-06-29 14:49:49 -03:00
|
|
|
const wrapper = mount(HomeView, {
|
2026-04-11 00:02:26 -03:00
|
|
|
global: {
|
|
|
|
|
stubs: {
|
2026-06-29 13:23:07 -03:00
|
|
|
RouterLink: RouterLinkStub,
|
|
|
|
|
PlatformSection: true,
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
props: {
|
2026-06-29 13:23:07 -03:00
|
|
|
labStatus: null,
|
|
|
|
|
loading: false,
|
|
|
|
|
error: "Live data unavailable",
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.text()).toContain("Live status is temporarily unavailable");
|
|
|
|
|
expect(wrapper.text()).not.toContain("status 500");
|
2026-04-11 00:02:26 -03:00
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
it("shows authenticated member actions without exposing service secrets", async () => {
|
|
|
|
|
resetAuth();
|
|
|
|
|
auth.authenticated = true;
|
|
|
|
|
auth.username = "ada";
|
|
|
|
|
auth.email = "ada@example.dev";
|
|
|
|
|
|
2026-06-29 14:49:49 -03:00
|
|
|
const wrapper = mount(HomeView, {
|
2026-06-29 13:23:07 -03:00
|
|
|
global: {
|
|
|
|
|
stubs: {
|
|
|
|
|
RouterLink: RouterLinkStub,
|
|
|
|
|
PlatformSection: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
props: {
|
|
|
|
|
labStatus: { connected: true, atlas: { up: true, known: true } },
|
|
|
|
|
loading: false,
|
|
|
|
|
error: "",
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
await flushPromises();
|
|
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain("Open Services");
|
|
|
|
|
expect(wrapper.text()).toContain("Account");
|
|
|
|
|
expect(wrapper.text()).not.toContain("app_password");
|
|
|
|
|
expect(wrapper.text()).not.toContain("mailu_app_password");
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("PlatformSection", () => {
|
|
|
|
|
it("preserves platform service, status, metric, and diagram behavior", () => {
|
|
|
|
|
const wrapper = shallowMount(PlatformSection, {
|
2026-04-11 00:02:26 -03:00
|
|
|
global: {
|
|
|
|
|
stubs: {
|
|
|
|
|
MetricRow: true,
|
|
|
|
|
ServiceGrid: true,
|
|
|
|
|
MermaidCard: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
props: {
|
2026-06-29 13:23:07 -03:00
|
|
|
labStatus: {
|
|
|
|
|
connected: true,
|
|
|
|
|
atlas: { up: true, known: true },
|
|
|
|
|
dedicated_hosts: {
|
|
|
|
|
known: true,
|
|
|
|
|
up: false,
|
|
|
|
|
up_count: 1,
|
|
|
|
|
total: 2,
|
|
|
|
|
hosts: [
|
|
|
|
|
{ label: "Database node", known: true, up: true },
|
|
|
|
|
{ label: "Jumphost", known: true, up: false },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
serviceData: {
|
|
|
|
|
services: [
|
|
|
|
|
{ name: "Nextcloud", summary: "Storage", link: "https://cloud.example.dev" },
|
|
|
|
|
{ name: "Monero", summary: "Node", link: "/monero", host: "monerod.crypto.svc.cluster.local:18081" },
|
|
|
|
|
{ name: "Mystery", summary: "Default", link: "https://default.example.dev" },
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
loading: true,
|
|
|
|
|
error: "",
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.vm.atlasPillClass).toBe("pill-ok");
|
|
|
|
|
expect(wrapper.vm.dedicatedHostsPillClass).toBe("pill-bad");
|
|
|
|
|
expect(wrapper.vm.dedicatedHostsLabel).toBe("1/2 responding");
|
|
|
|
|
expect(wrapper.vm.dedicatedHostItems.map((host) => host.value)).toEqual(["Responding", "Needs attention"]);
|
|
|
|
|
expect(wrapper.get(".status").text()).toBe("Loading...");
|
|
|
|
|
expect(wrapper.vm.displayServices.map((service) => service.icon)).toEqual(["NC", "XM", "TL"]);
|
|
|
|
|
expect(wrapper.vm.displayServices.find((service) => service.name === "Monero").host).toBe("/monero");
|
|
|
|
|
expect(wrapper.vm.hardwareDiagram).toContain("Titan Lab");
|
|
|
|
|
expect(wrapper.vm.hardwareDiagram).toContain("Dedicated non-cluster hosts");
|
|
|
|
|
expect(wrapper.vm.networkDiagram).toContain("oauth2-proxy");
|
|
|
|
|
expect(wrapper.vm.pipelineDiagram).toContain("Flux");
|
|
|
|
|
expect(wrapper.text()).toContain("IaC source");
|
|
|
|
|
expect(wrapper.text()).toContain("Titan Lab: Live Platform");
|
2026-04-11 00:02:26 -03:00
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
it("shows public status errors without raw API detail", () => {
|
|
|
|
|
const wrapper = shallowMount(PlatformSection, {
|
2026-04-11 00:02:26 -03:00
|
|
|
global: {
|
|
|
|
|
stubs: {
|
|
|
|
|
MetricRow: true,
|
|
|
|
|
ServiceGrid: true,
|
|
|
|
|
MermaidCard: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
props: {
|
2026-06-29 13:23:07 -03:00
|
|
|
labStatus: { connected: false, atlas: { up: false, known: false } },
|
2026-04-11 00:02:26 -03:00
|
|
|
serviceData: { services: [] },
|
2026-06-29 13:23:07 -03:00
|
|
|
metricsData: { items: [{ label: "Custom", value: "1", note: "" }] },
|
2026-04-11 00:02:26 -03:00
|
|
|
loading: false,
|
2026-06-29 13:23:07 -03:00
|
|
|
error: "Live data unavailable",
|
2026-04-11 00:02:26 -03:00
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.vm.atlasPillClass).toBe("pill-bad");
|
2026-04-11 00:02:26 -03:00
|
|
|
expect(wrapper.get(".status").text()).toBe("Live data unavailable");
|
|
|
|
|
expect(wrapper.vm.metricItems[0].note).toBe("");
|
2026-06-29 13:23:07 -03:00
|
|
|
expect(wrapper.text()).toContain("The platform overview remains available below");
|
2026-04-11 00:02:26 -03:00
|
|
|
});
|
|
|
|
|
});
|