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 SW.Bitween.Api/Domain/Accounts/Role.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class Role : BaseEntity, IAudited
public const int ViewerId = 3;

/// <summary>The groups the built-in Member and Viewer roles reach; Administration stays admin-only.</summary>
private static readonly string[] NonAdminGroups = ["Operate", "Integrations", "Configuration"];
private static readonly string[] NonAdminGroups = ["Operate", "Subscriptions", "Configuration"];

private Role()
{
Expand Down
22 changes: 11 additions & 11 deletions SW.Bitween.Sdk/Model/Permissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,46 +178,46 @@ private static PermissionAreaModel Area(string id, string label, string group, s
Area("dashboard", "Dashboard", "Operate", "Traffic and health overview (reached from the logo).",
(View, "See the dashboard.")),

// ——— Integrations ———
Area("subscriptions", "Integrations", "Integrations", "The configured pipelines that process exchanges.",
(View, "Browse integrations and their configuration."),
(Create, "Create integrations."),
// ——— Subscriptions ———
Area("subscriptions", "Subscriptions", "Subscriptions", "The configured pipelines that process exchanges.",
(View, "Browse subscriptions and their configuration."),
(Create, "Create subscriptions."),
(Edit, "Change adapters, mappings and settings."),
(Delete, "Delete integrations."),
(Delete, "Delete subscriptions."),
(Operate, "Pause, resume, receive now, aggregate now.")),

Area("partners", "Partners", "Integrations", "The external parties you exchange data with.",
Area("partners", "Partners", "Subscriptions", "The external parties you exchange data with.",
(View, "Browse partners and their properties."),
(Create, "Create partners."),
(Edit, "Change partner details, properties and API keys."),
(Delete, "Delete partners.")),

Area("documents", "Information types", "Integrations",
Area("documents", "Information types", "Subscriptions",
"The kinds of business documents that flow between partners.",
(View, "Browse information types."),
(Create, "Create information types."),
(Edit, "Change information types, codes and promoted properties."),
(Delete, "Delete unused information types.")),

Area("global-values", "Global values", "Integrations", "Shared value sets adapters can reference.",
Area("global-values", "Global values", "Subscriptions", "Shared value sets adapters can reference.",
(View, "Browse global value sets."),
(Create, "Create value sets."),
(Edit, "Change value sets."),
(Delete, "Delete value sets.")),

Area("notifiers", "Notifiers", "Integrations", "Alerts sent when exchanges fail or succeed.",
Area("notifiers", "Notifiers", "Subscriptions", "Alerts sent when exchanges fail or succeed.",
(View, "Browse notifiers and their delivery history."),
(Create, "Create notifiers."),
(Edit, "Change notifiers."),
(Delete, "Remove notifiers.")),

Area("api-gateways", "API gateways", "Integrations", "HTTP entry points partners call into.",
Area("api-gateways", "API gateways", "Subscriptions", "HTTP entry points partners call into.",
(View, "Browse API gateways and attached partners."),
(Create, "Create new API gateways."),
(Edit, "Change gateways and partner attachments."),
(Delete, "Delete API gateways.")),

Area("bus-gateways", "Bus gateways", "Integrations", "Bus listeners that route documents to integrations.",
Area("bus-gateways", "Bus gateways", "Subscriptions", "Bus listeners that route documents to subscriptions.",
(View, "Browse bus gateways and routes."),
(Create, "Create new bus gateways."),
(Edit, "Change gateways and routes."),
Expand Down
4 changes: 2 additions & 2 deletions SW.Bitween.Web/ClientApp/e2e/exchanges.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ test("exchanges list, filter, retry, bulk retry, create", async ({ page }) => {
await page.getByRole("dialog").getByRole("button", { name: "Retry" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0, { timeout: 15000 });

// Manually create an exchange addressed at an integration.
// Manually create an exchange addressed at a subscription.
await page.goto("exchanges/new");
await page.getByRole("combobox", { name: "Pick an integration…" }).click();
await page.getByRole("combobox", { name: "Pick a subscription…" }).click();
await page.getByRole("option", { name: "s3 test sub" }).click();
// Dismiss the dropdown panel via an outside click (it sits above the panel's
// anchor point, so it can't itself be covered) rather than Escape, which
Expand Down
36 changes: 18 additions & 18 deletions SW.Bitween.Web/ClientApp/e2e/gateways.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test.beforeEach(async ({ page }) => {
await page.waitForURL((url) => !url.pathname.endsWith("/login"), { timeout: 15000 });
});

test("API gateway: create, attach partner, create integration detour, edit attachment, detach, delete", async ({
test("API gateway: create, attach partner, create subscription detour, edit attachment, detach, delete", async ({
page,
}) => {
const name = `Playwright API GW ${Date.now()}`;
Expand All @@ -22,46 +22,46 @@ test("API gateway: create, attach partner, create integration detour, edit attac
await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
await expect(page.getByRole("heading", { name })).toBeVisible();

// Attach a partner, detouring to create the required GatewayApiCall integration inline.
// Attach a partner, detouring to create the required GatewayApiCall subscription inline.
await page.getByRole("button", { name: "Attach partner" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attach$/);

await page.getByRole("button", { name: "acme" }).click();
await page.getByRole("button", { name: "Continue" }).click();

const integrationName = `Playwright GW Integration ${Date.now()}`;
await page.getByRole("link", { name: "New integration" }).click();
const subscriptionName = `Playwright GW Subscription ${Date.now()}`;
await page.getByRole("link", { name: "New subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions\/new\?type=GatewayApiCall/);
await page.fill("#ni-name", integrationName);
await page.fill("#ni-name", subscriptionName);
await page.getByRole("button", { name: "test doc" }).click();
await page.getByLabel("handler adapter").click();
await page.getByRole("option", { name: "NativeHttpHandler" }).click();
await expect(page.getByRole("listbox")).toHaveCount(0, { timeout: 10000 });
await page.locator("#prop-Url").fill("https://example.com/sink");
await page.getByRole("button", { name: "Create integration" }).click();
await page.getByRole("button", { name: "Create subscription" }).click();

// This page itself renders a ReturnBanner with a "Continue" button before
// the mutation resolves (inherited from the detour link) — wait for the
// create to actually land on the new integration's own page first, or the
// create to actually land on the new subscription's own page first, or the
// click races and hits that stale button instead.
await expect(page).toHaveURL(/\/subscriptions\/\d+\?/);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attach$/);
await expect(page.getByText(integrationName)).toBeVisible();
await expect(page.getByText(subscriptionName)).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Attach partner" }).click();

await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
await expect(page.getByText("acme").first()).toBeVisible();
await expect(page.getByText(integrationName)).toBeVisible();
await expect(page.getByText(subscriptionName)).toBeVisible();

// Edit the attachment — exercises the remove-then-add path (backend's
// updatepartner can't mutate a composite-key column in place).
await page.getByRole("button", { name: "Edit attachment for acme" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+\/attachments\/\d+$/);
await page.getByRole("button", { name: "Save" }).click();
await expect(page).toHaveURL(/\/api-gateways\/\d+$/);
await expect(page.getByText(integrationName)).toBeVisible();
await expect(page.getByText(subscriptionName)).toBeVisible();

// Detach.
await page.getByRole("button", { name: "Detach acme" }).click();
Expand All @@ -80,7 +80,7 @@ test("API gateway: create, attach partner, create integration detour, edit attac
.getByRole("button", { name: "Delete gateway" })
.click();
// ApiGatewayPage navigates to /api-gateways, which the router redirects to
// the unified integrations list.
// the unified subscriptions list.
await expect(page).toHaveURL(/\/subscriptions\?types=api-gateways$/);
});

Expand All @@ -103,28 +103,28 @@ test("Bus gateway: create, add route with match expression, edit route, remove,
await page.getByRole("button", { name: "No partner" }).click();
await page.getByRole("button", { name: "Continue" }).click();

// Integration step — detour to create the required BusGateway integration.
const integrationName = `Playwright Bus Integration ${Date.now()}`;
await page.getByRole("link", { name: "New integration" }).click();
// Subscription step — detour to create the required BusGateway subscription.
const subscriptionName = `Playwright Bus Subscription ${Date.now()}`;
await page.getByRole("link", { name: "New subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions\/new\?type=BusGateway/);
await page.fill("#ni-name", integrationName);
await page.fill("#ni-name", subscriptionName);
await page.getByLabel("handler adapter").click();
await page.getByRole("option", { name: "NativeHttpHandler" }).click();
await expect(page.getByRole("listbox")).toHaveCount(0, { timeout: 10000 });
await page.locator("#prop-Url").fill("https://example.com/sink");
await page.getByRole("button", { name: "Create integration" }).click();
await page.getByRole("button", { name: "Create subscription" }).click();

// Wait for the create to actually land (see the comment in the API gateway
// test above) before clicking the ReturnBanner's "Continue".
await expect(page).toHaveURL(/\/subscriptions\/\d+\?/);
await page.getByRole("button", { name: "Continue" }).click();
await expect(page).toHaveURL(/\/bus-gateways\/\d+\/add-route$/);
await expect(page.getByText(integrationName)).toBeVisible();
await expect(page.getByText(subscriptionName)).toBeVisible();
await page.getByRole("button", { name: "Continue" }).click();
await page.getByRole("button", { name: "Add route" }).click();

await expect(page).toHaveURL(/\/bus-gateways\/\d+$/);
await expect(page.getByText(integrationName)).toBeVisible();
await expect(page.getByText(subscriptionName)).toBeVisible();

// Edit the route (no-op save exercises the round trip of a null match expression).
await page.getByRole("button", { name: /Edit route \d+/ }).click();
Expand Down
4 changes: 2 additions & 2 deletions SW.Bitween.Web/ClientApp/e2e/permissions-enforcement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ test("a custom role grants exactly what was ticked, in the nav, by URL, and at t

// 1. The sidebar offers the one page they can see, and nothing else.
await expect(sidebarLinks(page).filter({ hasText: "Exchanges" })).toBeVisible();
for (const hidden of ["Partners", "Integrations", "Work groups", "Team", "Settings"])
for (const hidden of ["Partners", "Subscriptions", "Work groups", "Team", "Settings"])
await expect(sidebarLinks(page).filter({ hasText: hidden })).toHaveCount(0);

// 2. Typing the URL of a page they lack doesn't get them in.
Expand Down Expand Up @@ -109,7 +109,7 @@ test("Viewer can read but not write", async ({ page }) => {
await removeMember(page, email);
});

test("Member can configure integrations but not manage the team", async ({ page }) => {
test("Member can configure subscriptions but not manage the team", async ({ page }) => {
await signInAsAdmin(page);
const email = await addMember(page, { name: "Regular Member", roles: ["Member"] });

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ test("scheduled job create, adapters, pause/resume, receive now, list, delete",

// Pause / resume.
await page.getByRole("button", { name: "Pause" }).click();
await page.getByRole("dialog", { name: "Pause this integration?" }).getByRole("button", { name: "Pause" }).click();
await page.getByRole("dialog", { name: "Pause this subscription?" }).getByRole("button", { name: "Pause" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByText("Paused", { exact: true }).first()).toBeVisible();

await page.getByRole("button", { name: "Resume" }).click();
await page.getByRole("dialog", { name: "Resume this integration?" }).getByRole("button", { name: "Resume" }).click();
await page.getByRole("dialog", { name: "Resume this subscription?" }).getByRole("button", { name: "Resume" }).click();
await expect(page.getByRole("dialog")).toHaveCount(0);
await expect(page.getByText("Paused", { exact: true })).toHaveCount(0);

Expand All @@ -73,7 +73,7 @@ test("scheduled job create, adapters, pause/resume, receive now, list, delete",
await row.getByRole("button", { name: `Open ${name}` }).click();
await expect(page).toHaveURL(/\/subscriptions\/\d+$/);
await page.getByRole("button", { name: "Delete" }).click();
await page.getByRole("button", { name: "Delete integration" }).click();
await page.getByRole("button", { name: "Delete subscription" }).click();
await expect(page).toHaveURL(/\/subscriptions$/);
await expect(page.getByText(name)).toHaveCount(0);
});
8 changes: 4 additions & 4 deletions SW.Bitween.Web/ClientApp/e2e/view-guards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,19 +91,19 @@ test("lookup mode stays readable, because pickers across the app depend on it",
});

test("a page still loads when the area behind its Used by count is refused", async ({ page }) => {
const roleName = `PW No Integrations ${Date.now()}`;
const roleName = `PW No Subscriptions ${Date.now()}`;
await signInAsAdmin(page);
await createRole(page, {
name: roleName,
permissions: [{ area: "Information types", action: "View" }],
});
const email = await addMember(page, { name: "No Integrations", roles: [roleName] });
const email = await addMember(page, { name: "No Subscriptions", roles: [roleName] });

await signOut(page);
await signIn(page, email, FIRST_PASSWORD);

// The information types list counts how many integrations use each type, which needs the
// integrations list this role can't read. The count is what's expendable, not the page.
// The information types list counts how many subscriptions use each type, which needs the
// subscriptions list this role can't read. The count is what's expendable, not the page.
await page.goto("information-types");
await expect(page.getByText("You don't have access to this page")).toHaveCount(0);
await expect(page.getByRole("table")).toBeVisible();
Expand Down
Loading
Loading