| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import type { InstallRequest } from "@g1t/contracts"; |
| 5 | import { connectorById, connectorsFor } from "@g1t/contracts/connectors"; |
| 6 | |
| 7 | import { FIRST_PARTY_EXTENSIONS, extensionById } from "@g1t/contracts/marketplace"; |
| 8 | |
| 9 | import { |
| 10 | STARTER_KITS, |
| 11 | TIERS, |
| 12 | addPath, |
| 13 | categoryTitle, |
| 14 | comingIntegrations, |
| 15 | comingListings, |
| 16 | extensionListings, |
| 17 | integrationListings, |
| 18 | integrationMatches, |
| 19 | integrationUses, |
| 20 | isConnectedSystem, |
| 21 | marketplaceForm, |
| 22 | passes, |
| 23 | readFilters, |
| 24 | tiersShown, |
| 25 | } from "./marketplace.ts"; |
| 26 | import { modeOf } from "./workspace-nav.ts"; |
| 27 | |
| 28 | const request = (listing: string, by: string, status: InstallRequest["status"] = "open"): InstallRequest => ({ |
| 29 | id: `ins_${listing}_${by}`, |
| 30 | listing, |
| 31 | kind: listing.startsWith("extension:") ? "extension" : "integration", |
| 32 | name: listing, |
| 33 | note: null, |
| 34 | requested_by: by, |
| 35 | requested_at: "2026-10-10T12:00:00Z", |
| 36 | status, |
| 37 | resolved_by: null, |
| 38 | resolved_at: null, |
| 39 | }); |
| 40 | |
| 41 | test("only integrations a workspace can connect today are listed, connected first", () => { |
| 42 | const views = connectorsFor("workspace"); |
| 43 | const listings = integrationListings(views, { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }, [], "ana", "acme"); |
| 44 | assert.ok(listings.every((l) => l.view.status === "available")); |
| 45 | assert.equal(listings[0]!.view.id, "sentry"); |
| 46 | assert.equal(listings[0]!.href, "/acme/-/integrations/alerts", "managed where it is managed"); |
| 47 | const linear = listings.find((l) => l.view.id === "linear")!; |
| 48 | assert.equal(linear.href, "/acme/-/integrations/trackers?add=linear#add", "connected on its setup page"); |
| 49 | assert.ok(!listings.some((l) => l.view.id === "slack")); |
| 50 | assert.ok(comingIntegrations(views).some((v) => v.id === "slack")); |
| 51 | assert.ok(integrationMatches(linear, "tracker")); |
| 52 | assert.ok(!integrationMatches(linear, "deploys")); |
| 53 | }); |
| 54 | |
| 55 | test("an owner adds what a request asks for where it is added", () => { |
| 56 | const views = connectorsFor("workspace"); |
| 57 | assert.equal(addPath({ listing: "agent:qa" }, "acme", views), null, "agents aren't added from the Marketplace"); |
| 58 | assert.equal(addPath({ listing: "integration:jira" }, "acme", views), "/acme/-/integrations/trackers?add=jira#add"); |
| 59 | assert.equal(addPath({ listing: "nonsense" }, "acme", views), null); |
| 60 | }); |
| 61 | |
| 62 | test("the Marketplace's forms are read strictly", () => { |
| 63 | const form = (fields: Record<string, string>) => { |
| 64 | const data = new FormData(); |
| 65 | for (const [k, v] of Object.entries(fields)) data.set(k, v); |
| 66 | return data; |
| 67 | }; |
| 68 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "integration:jira", note: " flaky " })), { intent: "request", listing: "integration:jira", note: "flaky" }); |
| 69 | assert.deepEqual(marketplaceForm(form({ intent: "request", listing: "extension:support" })), { intent: "request", listing: "extension:support", note: null }); |
| 70 | assert.equal(marketplaceForm(form({ intent: "request", listing: "agent:qa" })), null, "agents start from templates in Agents"); |
| 71 | assert.equal(marketplaceForm(form({ intent: "request", listing: "app:qa" })), null); |
| 72 | assert.deepEqual(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "declined" })), { intent: "resolve", id: "ins_1", status: "declined" }); |
| 73 | assert.equal(marketplaceForm(form({ intent: "resolve", id: "ins_1", status: "open" })), null); |
| 74 | assert.equal(marketplaceForm(form({ intent: "install" })), null); |
| 75 | }); |
| 76 | |
| 77 | test("extensions list published ones first, each with its install", () => { |
| 78 | const published = { ...FIRST_PARTY_EXTENSIONS[1]!, id: "standup", status: "available" as const, source: { repo: "flagon-io/standup", tag: "v1.0.0" }, version: "1.0.0" }; |
| 79 | const install = { id: "ins_1", listing: "extension:standup", version: "1.0.0", plan: "free", installed_by: "chase", installed_at: "2026-10-10T00:00:00Z", enabled: true, disabled_by: null, disabled_at: null, budget_monthly_micros: null }; |
| 80 | const listings = extensionListings([...FIRST_PARTY_EXTENSIONS, published], [install], [request("extension:support", "ana")], "ana"); |
| 81 | assert.equal(listings[0]!.manifest.id, "standup"); |
| 82 | assert.equal(listings[0]!.install?.version, "1.0.0"); |
| 83 | assert.equal(listings.find((l) => l.manifest.id === "support")!.requested, true); |
| 84 | assert.equal(listings.find((l) => l.manifest.id === "mail")!.install, null); |
| 85 | }); |
| 86 | |
| 87 | test("coming integrations include the ones each person connects, once", () => { |
| 88 | const workspace = connectorsFor("workspace"); |
| 89 | const personal = connectorsFor("personal"); |
| 90 | const coming = comingIntegrations(workspace, personal).map((v) => v.id); |
| 91 | assert.ok(coming.includes("slack")); |
| 92 | assert.ok(coming.includes("gmail"), "Gmail is each person's"); |
| 93 | assert.ok(coming.includes("google-calendar")); |
| 94 | assert.equal(new Set(coming).size, coming.length); |
| 95 | }); |
| 96 | |
| 97 | test("installing, switching and removing an extension are read from forms", () => { |
| 98 | const form = (fields: Record<string, string>) => { |
| 99 | const data = new FormData(); |
| 100 | for (const [k, v] of Object.entries(fields)) data.set(k, v); |
| 101 | return data; |
| 102 | }; |
| 103 | assert.deepEqual(marketplaceForm(form({ intent: "install", extension: "support" })), { intent: "install", extension: "support" }); |
| 104 | assert.deepEqual(marketplaceForm(form({ intent: "switch", listing: "extension:support", enabled: "off" })), { intent: "switch", listing: "extension:support", enabled: false }); |
| 105 | assert.equal(marketplaceForm(form({ intent: "switch", listing: "integration:sentry", enabled: "off" })), null); |
| 106 | assert.deepEqual(marketplaceForm(form({ intent: "uninstall", listing: "extension:support" })), { intent: "uninstall", listing: "extension:support" }); |
| 107 | assert.equal(addPath({ listing: "extension:mail" }, "acme", []), "/acme/-/marketplace/extensions/mail"); |
| 108 | }); |
| 109 | |
| 110 | test("starter kits name extensions that are listed, and connected systems are told apart", () => { |
| 111 | for (const kit of STARTER_KITS) { |
| 112 | assert.ok(kit.extensions.length > 0, kit.id); |
| 113 | for (const id of kit.extensions) assert.ok(extensionById(id), `${kit.id}: ${id}`); |
| 114 | } |
| 115 | const systems = FIRST_PARTY_EXTENSIONS.filter(isConnectedSystem).map((m) => m.id); |
| 116 | assert.deepEqual(systems, ["helpdesk-bridge", "crm-bridge", "erp-bridge"]); |
| 117 | assert.ok(FIRST_PARTY_EXTENSIONS.filter(isConnectedSystem).every((m) => m.bridges), "a connected system says what it bridges"); |
| 118 | }); |
| 119 | |
| 120 | test("every listing says who builds it and whether it can be added here", () => { |
| 121 | const views = connectorsFor("workspace"); |
| 122 | const connected = { sentry: { detail: "acme", problem: null, manage: "/acme/-/integrations/alerts" } }; |
| 123 | const listings = integrationListings(views, connected, [], "ana", "acme", { github: "This g1t has no GitHub App set up." }); |
| 124 | assert.ok(listings.every((l) => l.tier === "official" && l.publisher === "g1t"), "the connector catalog is g1t's own"); |
| 125 | assert.equal(listings.find((l) => l.view.id === "sentry")!.availability, "added"); |
| 126 | assert.equal(listings.find((l) => l.view.id === "jira")!.availability, "available"); |
| 127 | const github = listings.find((l) => l.view.id === "github")!; |
| 128 | assert.equal(github.availability, "unavailable"); |
| 129 | assert.equal(github.why, "This g1t has no GitHub App set up."); |
| 130 | assert.equal(github.href, null, "nothing to connect where it can't be connected"); |
| 131 | assert.equal(listings.find((l) => l.view.id === "linear")!.path, "/acme/-/marketplace/integrations/linear"); |
| 132 | |
| 133 | const coming = comingListings(views, connectorsFor("personal").filter((v) => v.id === "gmail"), "acme"); |
| 134 | assert.ok(coming.every((l) => l.availability === "soon" && l.href === null && l.tier === "official")); |
| 135 | assert.equal(coming.find((l) => l.view.id === "gmail")!.scope, "personal"); |
| 136 | |
| 137 | const extensions = extensionListings(FIRST_PARTY_EXTENSIONS, [], [], "ana"); |
| 138 | assert.ok(extensions.every((l) => l.tier === "official" && l.availability === "soon"), "g1t's own, none published yet"); |
| 139 | }); |
| 140 | |
| 141 | test("an integration's page says what it does today and what is still Soon", () => { |
| 142 | const linear = integrationUses(connectorById("linear")!); |
| 143 | assert.deepEqual(linear.today.map((u) => u.scope), ["workspace"]); |
| 144 | assert.deepEqual(linear.today[0]!.capabilities, ["Agents can read", "Writes back"]); |
| 145 | assert.deepEqual(linear.soon.map((u) => u.scope), ["personal"], "each person's Linear inbox is Soon"); |
| 146 | const slack = integrationUses(connectorById("slack")!); |
| 147 | assert.equal(slack.today.length, 0); |
| 148 | assert.equal(slack.soon.length, 2); |
| 149 | assert.equal(categoryTitle("issues"), "Issues & projects"); |
| 150 | }); |
| 151 | |
| 152 | test("filters by tier and availability are read from the address, and unknown ones are All", () => { |
| 153 | assert.deepEqual(readFilters(new URLSearchParams("tier=verified&availability=soon")), { tier: "verified", availability: "soon" }); |
| 154 | assert.deepEqual(readFilters(new URLSearchParams("tier=gold&availability=maybe")), { tier: "all", availability: "all" }); |
| 155 | assert.deepEqual(tiersShown({ tier: "all", availability: "all" }), ["official", "verified", "community", "internal"]); |
| 156 | assert.deepEqual(tiersShown({ tier: "community", availability: "all" }), ["community"]); |
| 157 | assert.ok(passes({ tier: "official", availability: "soon" }, { tier: "all", availability: "soon" })); |
| 158 | assert.ok(!passes({ tier: "official", availability: "soon" }, { tier: "verified", availability: "all" })); |
| 159 | for (const tier of ["verified", "community", "internal"] as const) assert.ok(TIERS[tier].none.extension.length > 0, `${tier} says when it is empty`); |
| 160 | }); |
| 161 | |
| 162 | test("the Marketplace is part of Apps, not a mode of its own", () => { |
| 163 | assert.equal(modeOf("/acme/-/marketplace", "acme"), "apps"); |
| 164 | assert.equal(modeOf("/acme/-/marketplace/extensions/mail", "acme"), "apps"); |
| 165 | assert.equal(modeOf("/acme/-/agents/templates", "acme"), "agents", "agent templates are Agents'"); |
| 166 | }); |