Adds a safe Bird Control Plane configuration surface with read-only health, credential redaction, protected capability switches, canonical workspace support, and focused frontend coverage. Required CI passed on headcc04117d6bagainst master3de5215b5e.
217 lines
7.1 KiB
JavaScript
217 lines
7.1 KiB
JavaScript
// @vitest-environment jsdom
|
|
|
|
import { flushPromises, mount } from "@vue/test-utils";
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
const birdMocks = vi.hoisted(() => ({
|
|
getAll: vi.fn(),
|
|
getStatus: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("@/components/displays/superuser/configuration/ConfigurationCategory.vue", () => ({
|
|
default: {
|
|
props: ["title", "subtitle"],
|
|
template: '<section class="configuration-category"><h2>{{ title }}</h2><p>{{ subtitle }}</p><slot /></section>',
|
|
},
|
|
}));
|
|
vi.mock("@/components/displays/superuser/configuration/ConfigurationInput.vue", () => ({
|
|
default: {
|
|
props: ["title", "value"],
|
|
template: '<div class="configuration-input">{{ title }}:{{ value }}</div>',
|
|
},
|
|
}));
|
|
vi.mock("@/components/displays/superuser/configuration/ConfigurationSecretKey.vue", () => ({
|
|
default: {
|
|
props: ["isSet"],
|
|
template: '<div data-testid="bird-secret-state">{{ isSet ? "set" : "unset" }}</div>',
|
|
},
|
|
}));
|
|
vi.mock("@/components/displays/superuser/configuration/ConfigurationSwitch.vue", () => ({
|
|
default: { template: "<div />" },
|
|
}));
|
|
vi.mock("@/components/global/PageTitle.vue", () => ({
|
|
default: { template: "<header />" },
|
|
}));
|
|
vi.mock("@/components/page/wrappers/RestrictedPageWrapper.vue", () => ({
|
|
default: { template: "<div><slot /></div>" },
|
|
}));
|
|
vi.mock("@/views/dashboards/superUserDashboard/configuration/ConfigurationSubPageWrapper.vue", () => ({
|
|
default: { template: '<main><slot name="title" /><slot name="content" /></main>' },
|
|
}));
|
|
|
|
vi.mock("@/components/session/token/SessionUser.vue", () => ({
|
|
SessionUser: {
|
|
canAccessSuperUser: () => true,
|
|
superUser: {
|
|
modules: {
|
|
bird: {
|
|
config: {
|
|
get_all: birdMocks.getAll,
|
|
enabled: { set: vi.fn() },
|
|
keys: {
|
|
api_key: { set: vi.fn() },
|
|
server_url: { set: vi.fn() },
|
|
workspaceId: { set: vi.fn() },
|
|
channelId: { set: vi.fn() },
|
|
participantId: { set: vi.fn() },
|
|
allowed_channel_ids_json: { set: vi.fn() },
|
|
control_plane_enabled: { set: vi.fn() },
|
|
control_plane_token: { set: vi.fn() },
|
|
webhook_signing_key: { set: vi.fn() },
|
|
webhook_public_url: { set: vi.fn() },
|
|
webhook_replay_window_seconds: { set: vi.fn() },
|
|
flow_enabled: { set: vi.fn() },
|
|
flow_shared_secret: { set: vi.fn() },
|
|
flow_policy_json: { set: vi.fn() },
|
|
operations_actions_enabled: { set: vi.fn() },
|
|
outbound_messages_enabled: { set: vi.fn() },
|
|
template_policy_json: { set: vi.fn() },
|
|
},
|
|
},
|
|
functions: {
|
|
getHealth: birdMocks.getStatus,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
import ConfigurationBird from "@/views/dashboards/superUserDashboard/configuration/ConfigurationBird.vue";
|
|
|
|
const global = {
|
|
mocks: {
|
|
$t: (key) => key,
|
|
},
|
|
};
|
|
|
|
describe("Bird configuration", () => {
|
|
beforeEach(() => {
|
|
birdMocks.getAll.mockReset();
|
|
birdMocks.getStatus.mockReset();
|
|
});
|
|
|
|
it("renders healthy read-only status and provider channel state", async () => {
|
|
birdMocks.getAll.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: { variable: "enabled", value: true },
|
|
workspaceId: { variable: "workspaceId", value: "workspace-current" },
|
|
},
|
|
},
|
|
});
|
|
birdMocks.getStatus.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: true,
|
|
configured: true,
|
|
providerReachable: true,
|
|
workspaceId: "workspace-current",
|
|
channelId: "channel-1",
|
|
check: "channels.list",
|
|
channels: [{ id: "channel-1", name: "Customer WhatsApp", platform: "whatsapp", status: "active" }],
|
|
},
|
|
},
|
|
});
|
|
|
|
const wrapper = mount(ConfigurationBird, { global });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="bird-health-state"]').text()).toBe("configuration.bird.state.healthy");
|
|
expect(wrapper.text()).toContain("configuration.bird.read_only_health");
|
|
expect(wrapper.text()).toContain("channels.list");
|
|
expect(wrapper.text()).toContain("Customer WhatsApp");
|
|
expect(wrapper.text()).toContain("whatsapp");
|
|
expect(wrapper.text()).toContain("active");
|
|
});
|
|
|
|
it("uses workplaceId only as a display fallback while exposing the canonical field", async () => {
|
|
birdMocks.getAll.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: { variable: "enabled", value: false },
|
|
workplaceId: { variable: "workplaceId", value: "workspace-legacy" },
|
|
},
|
|
},
|
|
});
|
|
birdMocks.getStatus.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: false,
|
|
configured: false,
|
|
healthy: false,
|
|
},
|
|
},
|
|
});
|
|
|
|
const wrapper = mount(ConfigurationBird, { global });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="bird-health-state"]').text()).toBe("configuration.bird.state.disabled");
|
|
expect(wrapper.text()).toContain("configuration.bird.workspace_id:workspace-legacy");
|
|
});
|
|
|
|
it("fails closed to an unavailable health state", async () => {
|
|
birdMocks.getAll.mockRejectedValue(new Error("Config unavailable"));
|
|
birdMocks.getStatus.mockRejectedValue(new Error("Health unavailable"));
|
|
|
|
const wrapper = mount(ConfigurationBird, { global });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="bird-config-unavailable"]').text()).toBe("configuration.bird.config_unavailable");
|
|
expect(wrapper.get('[data-testid="bird-health-state"]').text()).toBe("configuration.bird.state.unavailable");
|
|
});
|
|
|
|
it("uses redacted secret metadata and never renders an accidentally returned secret value", async () => {
|
|
birdMocks.getAll.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: { variable: "enabled", value: true },
|
|
api_key: {
|
|
variable: "api_key",
|
|
value: "must-not-reach-the-dom",
|
|
isSecret: true,
|
|
isSet: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
birdMocks.getStatus.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: true,
|
|
configured: true,
|
|
healthy: true,
|
|
channels: [],
|
|
},
|
|
},
|
|
});
|
|
|
|
const wrapper = mount(ConfigurationBird, { global });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="bird-secret-state"]').text()).toBe("set");
|
|
expect(wrapper.text()).not.toContain("must-not-reach-the-dom");
|
|
});
|
|
|
|
it("redacts the legacy API key even before isSecret metadata is deployed", async () => {
|
|
birdMocks.getAll.mockResolvedValue({
|
|
data: {
|
|
data: {
|
|
enabled: { variable: "enabled", value: true },
|
|
api_key: { variable: "api_key", value: "legacy-raw-secret" },
|
|
},
|
|
},
|
|
});
|
|
birdMocks.getStatus.mockResolvedValue({
|
|
data: { data: { enabled: true, configured: true, healthy: true, channels: [] } },
|
|
});
|
|
|
|
const wrapper = mount(ConfigurationBird, { global });
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('[data-testid="bird-secret-state"]').text()).toBe("set");
|
|
expect(wrapper.text()).not.toContain("legacy-raw-secret");
|
|
});
|
|
});
|