89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { EntitiesApi } from "./EntitiesApi";
|
|
import { findEntityByName, findEntityBySlug, getEntityByPrefix } from "../../db/entities/entities.db.server";
|
|
|
|
vi.mock("../../db/entities/entities.db.server", () => ({
|
|
findEntityByName: vi.fn(),
|
|
findEntityBySlug: vi.fn(),
|
|
getEntityByIdOrName: vi.fn(),
|
|
getEntityByPrefix: vi.fn(),
|
|
}));
|
|
|
|
vi.mock("../../services/.server/subscriptionService", () => ({
|
|
getPlanFeatureUsage: vi.fn(),
|
|
}));
|
|
|
|
describe("EntitiesApi.validateEntity", () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(findEntityByName).mockResolvedValue(null);
|
|
vi.mocked(findEntityBySlug).mockResolvedValue(null);
|
|
vi.mocked(getEntityByPrefix).mockResolvedValue(null);
|
|
});
|
|
|
|
it("rejects entity slugs that would collide with canonical tenant child routes", async () => {
|
|
await expect(
|
|
EntitiesApi.validateEntity({
|
|
tenantId: null,
|
|
name: "settings",
|
|
slug: " Settings ",
|
|
order: null,
|
|
prefix: "set",
|
|
})
|
|
).resolves.toContain("Reserved entity slug ' Settings ' conflicts with a workspace route");
|
|
});
|
|
});
|
|
|
|
describe("EntitiesApi.getNoCodeRoutes", () => {
|
|
it("does not classify tenant slugs that only start with admin as admin routes", () => {
|
|
const routes = EntitiesApi.getNoCodeRoutes({
|
|
request: new Request("http://app.localhost:3000/adminish/g/sales/contact"),
|
|
params: { tenant: "adminish", group: "sales" },
|
|
});
|
|
|
|
expect(routes.list).toBe("/adminish/g/sales/:entity");
|
|
expect(routes.overview).toBe("/adminish/g/sales/:entity/:id");
|
|
});
|
|
|
|
it("uses existing no-code entity routes for admin grouped entity contexts", () => {
|
|
const routes = EntitiesApi.getNoCodeRoutes({
|
|
request: new Request("http://admin.localhost:3000/g/sales/contact"),
|
|
params: { group: "sales" },
|
|
});
|
|
|
|
expect(routes.list).toBe("/entities/:entity/no-code/:entity");
|
|
expect(routes.new).toBe("/entities/:entity/no-code/:entity/new");
|
|
expect(routes.overview).toBe("/entities/:entity/no-code/:entity/:id");
|
|
expect(routes.edit).toBe("/entities/:entity/no-code/:entity/:id/edit");
|
|
expect(routes.import).toBe("/entities/:entity/no-code/:entity/import");
|
|
expect(routes.export).toBe("/entities/:entity/no-code/:entity/export");
|
|
expect(routes.group).toBe("/entities/groups");
|
|
});
|
|
|
|
it("uses host context instead of removed admin path prefixes", () => {
|
|
const request = new Request("https://unknown.example.test/admin/entities/company/no-code/company");
|
|
|
|
const routes = EntitiesApi.getNoCodeRoutes({ request, params: { tenant: "acme" } });
|
|
|
|
expect(routes.list).toBe("/acme/:entity");
|
|
expect(routes.list).not.toBe("/entities/:entity/no-code/:entity");
|
|
});
|
|
|
|
it("does not treat removed tenant-prefixed runtime paths as app context for grouped routes", () => {
|
|
const request = new Request("https://unknown.example.test/app/acme/g/sales/company");
|
|
|
|
const routes = EntitiesApi.getNoCodeRoutes({ request, params: { tenant: "acme", group: "sales" } });
|
|
|
|
expect(routes.group).toBeUndefined();
|
|
expect(routes.list).toBe("/acme/:entity");
|
|
});
|
|
|
|
it("matches tenant CRM routes by exact path segment", () => {
|
|
const crmRequest = new Request("https://app.example.test/acme/crm/company");
|
|
const crmishRequest = new Request("https://app.example.test/acme/crmish/company");
|
|
|
|
expect(EntitiesApi.getNoCodeRoutes({ request: crmRequest, params: { tenant: "acme" } }).list).toBe("/acme/crm/:entity");
|
|
expect(EntitiesApi.getNoCodeRoutes({ request: crmishRequest, params: { tenant: "acme" } }).list).toBe("/acme/:entity");
|
|
});
|
|
});
|