mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
* fix(den): support legacy org invitation routes Restore the old org-scoped invitation paths and rehydrate sessions to a user's first organization when activeOrg is missing so older clients keep working. Include the shared types workspace in Den Docker images so the local verification stack can still build. * fix(den): proxy legacy org-scoped routes Route legacy '/v1/orgs/:orgId/*' requests into the new unscoped '/v1/*' handlers while preserving the current '/v1/orgs/...' endpoints. Pass the org id through middleware so old clients keep the intended workspace context and add coverage for the generic proxy behavior. * refactor(den): trim invitation route churn Drop the leftover invitation-specific handler extraction now that legacy org-scoped forwarding is handled generically in the org router. Keep the proxy behavior intact while returning the invitation routes closer to their original shape. --------- Co-authored-by: src-opn <src-opn@users.noreply.github.com>
96 lines
3.5 KiB
TypeScript
96 lines
3.5 KiB
TypeScript
import { beforeAll, expect, test } from "bun:test"
|
|
import { Hono } from "hono"
|
|
|
|
function seedRequiredEnv() {
|
|
process.env.DATABASE_URL = process.env.DATABASE_URL ?? "mysql://root:password@127.0.0.1:3306/openwork_test"
|
|
process.env.DEN_DB_ENCRYPTION_KEY = process.env.DEN_DB_ENCRYPTION_KEY ?? "x".repeat(32)
|
|
process.env.BETTER_AUTH_SECRET = process.env.BETTER_AUTH_SECRET ?? "y".repeat(32)
|
|
process.env.BETTER_AUTH_URL = process.env.BETTER_AUTH_URL ?? "http://127.0.0.1:8790"
|
|
process.env.CORS_ORIGINS = process.env.CORS_ORIGINS ?? "http://127.0.0.1:8790"
|
|
}
|
|
|
|
let invitationModule: typeof import("../src/routes/org/invitations.js")
|
|
let orgRoutesModule: typeof import("../src/routes/org/index.js")
|
|
let userOrganizationsModule: typeof import("../src/middleware/user-organizations.js")
|
|
|
|
beforeAll(async () => {
|
|
seedRequiredEnv()
|
|
invitationModule = await import("../src/routes/org/invitations.js")
|
|
orgRoutesModule = await import("../src/routes/org/index.js")
|
|
userOrganizationsModule = await import("../src/middleware/user-organizations.js")
|
|
})
|
|
|
|
function createOrgApp() {
|
|
const app = new Hono()
|
|
orgRoutesModule.registerOrgRoutes(app)
|
|
return app
|
|
}
|
|
|
|
test("legacy org-scoped paths proxy into the unscoped handlers", async () => {
|
|
const app = createOrgApp()
|
|
const response = await app.request("http://den.local/v1/orgs/org_123/invitations", {
|
|
body: JSON.stringify({ email: "teammate@example.com", role: "admin" }),
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
})
|
|
|
|
expect(response.status).toBe(401)
|
|
await expect(response.json()).resolves.toEqual({ error: "unauthorized" })
|
|
})
|
|
|
|
test("legacy org-scoped proxy also reaches non-invitation org resources", async () => {
|
|
const app = createOrgApp()
|
|
const response = await app.request("http://den.local/v1/orgs/org_123/teams", {
|
|
body: JSON.stringify({ memberIds: [], name: "Legacy Team" }),
|
|
headers: {
|
|
"content-type": "application/json",
|
|
},
|
|
method: "POST",
|
|
})
|
|
|
|
expect(response.status).toBe(401)
|
|
await expect(response.json()).resolves.toEqual({ error: "unauthorized" })
|
|
})
|
|
|
|
test("current org endpoints are not swallowed by the legacy proxy", async () => {
|
|
const app = createOrgApp()
|
|
const response = await app.request("http://den.local/v1/orgs/invitations/preview?id=bad", {
|
|
method: "GET",
|
|
})
|
|
|
|
expect(response.status).toBe(400)
|
|
})
|
|
|
|
test("invitation cancel still validates against the unscoped handler", async () => {
|
|
const app = new Hono()
|
|
invitationModule.registerOrgInvitationRoutes(app)
|
|
const response = await app.request("http://den.local/v1/invitations/invitation_123/cancel", {
|
|
method: "POST",
|
|
})
|
|
|
|
expect(response.status).toBe(401)
|
|
await expect(response.json()).resolves.toEqual({ error: "unauthorized" })
|
|
})
|
|
|
|
test("session hydration only runs when a user session is missing an active organization", () => {
|
|
expect(userOrganizationsModule.shouldHydrateSessionActiveOrganization({
|
|
scopedOrganizationId: null,
|
|
sessionActiveOrganizationId: null,
|
|
resolvedActiveOrganizationId: "organization_first",
|
|
})).toBe(true)
|
|
|
|
expect(userOrganizationsModule.shouldHydrateSessionActiveOrganization({
|
|
scopedOrganizationId: null,
|
|
sessionActiveOrganizationId: "organization_existing",
|
|
resolvedActiveOrganizationId: "organization_existing",
|
|
})).toBe(false)
|
|
|
|
expect(userOrganizationsModule.shouldHydrateSessionActiveOrganization({
|
|
scopedOrganizationId: "organization_scoped",
|
|
sessionActiveOrganizationId: null,
|
|
resolvedActiveOrganizationId: "organization_scoped",
|
|
})).toBe(false)
|
|
})
|