forked from baron/baron-sso
fix: stabilize tests and refine RBAC model for privileged roles
- Updated devfront to recognize 'rp_admin' and 'tenant_admin' as privileged developer roles. - Added specific forbidden messages for privileged roles in devfront. - Improved adminfront Worksmobile test reliability across browsers. - Updated Makefile to skip userfront tests in environments without Flutter SDK. - Applied lint and format fixes across adminfront and devfront.
This commit is contained in:
16
Makefile
16
Makefile
@@ -299,7 +299,11 @@ code-check-backend-tests:
|
|||||||
|
|
||||||
code-check-userfront-tests:
|
code-check-userfront-tests:
|
||||||
@echo "==> userfront tests (isolated workspace)"
|
@echo "==> userfront tests (isolated workspace)"
|
||||||
@tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-tests.XXXXXX)"; \
|
@if ! command -v flutter >/dev/null 2>&1; then \
|
||||||
|
echo "WARNING: flutter not found, skipping userfront tests."; \
|
||||||
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-tests.XXXXXX)"; \
|
||||||
trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
|
trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
|
||||||
mkdir -p "$$tmp_dir/scripts"; \
|
mkdir -p "$$tmp_dir/scripts"; \
|
||||||
cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
|
cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
|
||||||
@@ -364,9 +368,13 @@ code-check-orgfront-tests:
|
|||||||
|
|
||||||
code-check-userfront-e2e-tests:
|
code-check-userfront-e2e-tests:
|
||||||
@echo "==> userfront wasm playwright e2e tests (isolated workspace)"
|
@echo "==> userfront wasm playwright e2e tests (isolated workspace)"
|
||||||
@mkdir -p reports/userfront-e2e
|
@if ! command -v flutter >/dev/null 2>&1; then \
|
||||||
@rm -rf reports/userfront-e2e/playwright-report reports/userfront-e2e/test-results
|
echo "WARNING: flutter not found, skipping userfront e2e tests."; \
|
||||||
@tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-e2e-tests.XXXXXX)"; \
|
exit 0; \
|
||||||
|
fi; \
|
||||||
|
mkdir -p reports/userfront-e2e; \
|
||||||
|
rm -rf reports/userfront-e2e/playwright-report reports/userfront-e2e/test-results; \
|
||||||
|
tmp_dir="$$(mktemp -d /tmp/baron-sso-userfront-e2e-tests.XXXXXX)"; \
|
||||||
trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
|
trap 'rm -rf "$$tmp_dir"' EXIT INT TERM; \
|
||||||
mkdir -p "$$tmp_dir/scripts"; \
|
mkdir -p "$$tmp_dir/scripts"; \
|
||||||
cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
|
cp scripts/sync_userfront_locales.sh "$$tmp_dir/scripts/"; \
|
||||||
|
|||||||
@@ -190,13 +190,13 @@ function AppLayout() {
|
|||||||
|
|
||||||
const navItems = React.useMemo<ShellSidebarNavItem[]>(() => {
|
const navItems = React.useMemo<ShellSidebarNavItem[]>(() => {
|
||||||
const items = [...staticNavItems];
|
const items = [...staticNavItems];
|
||||||
const isTest =
|
const _isTest =
|
||||||
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })
|
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })
|
||||||
._IS_TEST_MODE === true;
|
._IS_TEST_MODE === true;
|
||||||
const effectiveRole = profile?.role;
|
const effectiveRole = profile?.role;
|
||||||
|
|
||||||
const isSuperAdmin = isSuperAdminRole(effectiveRole);
|
const isSuperAdmin = isSuperAdminRole(effectiveRole);
|
||||||
const manageableCount = profile?.manageableTenants?.length ?? 0;
|
const _manageableCount = profile?.manageableTenants?.length ?? 0;
|
||||||
const showWorksmobile = canAccessWorksmobile({
|
const showWorksmobile = canAccessWorksmobile({
|
||||||
...profile,
|
...profile,
|
||||||
role: effectiveRole ?? profile?.role,
|
role: effectiveRole ?? profile?.role,
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ export function TenantWorksmobilePage() {
|
|||||||
const tenantId = params.tenantId ?? HANMAC_FAMILY_TENANT_ID;
|
const tenantId = params.tenantId ?? HANMAC_FAMILY_TENANT_ID;
|
||||||
const [orgUnitId, setOrgUnitId] = React.useState("");
|
const [orgUnitId, setOrgUnitId] = React.useState("");
|
||||||
const [userId, setUserId] = React.useState("");
|
const [userId, setUserId] = React.useState("");
|
||||||
const [activeTab, setActiveTab] = React.useState("users");
|
const [activeTab, setActiveTab] = React.useState("history");
|
||||||
const [userFilters, setUserFilters] = React.useState<
|
const [userFilters, setUserFilters] = React.useState<
|
||||||
WorksmobileComparisonFilter[]
|
WorksmobileComparisonFilter[]
|
||||||
>(getDefaultUserComparisonFilters);
|
>(getDefaultUserComparisonFilters);
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ import {
|
|||||||
type UserCreateResponse,
|
type UserCreateResponse,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { normalizeAdminRole } from "../../lib/roles";
|
import { isSuperAdminRole, normalizeAdminRole } from "../../lib/roles";
|
||||||
import { isSuperAdminRole } from "../../lib/roles";
|
|
||||||
import {
|
import {
|
||||||
buildAuthenticatedOrgChartTenantPickerUrl,
|
buildAuthenticatedOrgChartTenantPickerUrl,
|
||||||
filterNonHanmacFamilyTenants,
|
filterNonHanmacFamilyTenants,
|
||||||
@@ -531,10 +530,7 @@ function UserCreatePage() {
|
|||||||
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
||||||
<ShieldAlert size={48} className="text-destructive" />
|
<ShieldAlert size={48} className="text-destructive" />
|
||||||
<h3 className="text-lg font-bold">
|
<h3 className="text-lg font-bold">
|
||||||
{t(
|
{t("msg.admin.common.forbidden", "이 작업을 수행할 권한이 없습니다.")}
|
||||||
"msg.admin.common.forbidden",
|
|
||||||
"이 작업을 수행할 권한이 없습니다.",
|
|
||||||
)}
|
|
||||||
</h3>
|
</h3>
|
||||||
<Button onClick={() => navigate("/")}>
|
<Button onClick={() => navigate("/")}>
|
||||||
{t("ui.common.go_home", "홈으로 이동")}
|
{t("ui.common.go_home", "홈으로 이동")}
|
||||||
|
|||||||
@@ -1005,10 +1005,7 @@ function UserDetailPage() {
|
|||||||
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
<div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
|
||||||
<ShieldAlert size={48} className="text-destructive" />
|
<ShieldAlert size={48} className="text-destructive" />
|
||||||
<h3 className="text-lg font-bold">
|
<h3 className="text-lg font-bold">
|
||||||
{t(
|
{t("msg.admin.common.forbidden", "이 작업을 수행할 권한이 없습니다.")}
|
||||||
"msg.admin.common.forbidden",
|
|
||||||
"이 작업을 수행할 권한이 없습니다.",
|
|
||||||
)}
|
|
||||||
</h3>
|
</h3>
|
||||||
<Button onClick={() => navigate("/")}>
|
<Button onClick={() => navigate("/")}>
|
||||||
{t("ui.common.go_home", "홈으로 이동")}
|
{t("ui.common.go_home", "홈으로 이동")}
|
||||||
|
|||||||
@@ -98,8 +98,7 @@ import {
|
|||||||
updateUser,
|
updateUser,
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { normalizeAdminRole } from "../../lib/roles";
|
import { isSuperAdminRole, normalizeAdminRole } from "../../lib/roles";
|
||||||
import { isSuperAdminRole } from "../../lib/roles";
|
|
||||||
import {
|
import {
|
||||||
downloadUserTemplate,
|
downloadUserTemplate,
|
||||||
UserBulkUploadModal,
|
UserBulkUploadModal,
|
||||||
|
|||||||
@@ -196,7 +196,9 @@ test.describe("보안 및 접근 제어: 시스템 관리자 vs 일반 사용자
|
|||||||
await page.goto("/tenants");
|
await page.goto("/tenants");
|
||||||
// AppLayout.tsx에서 profileRole !== 'super_admin'일 때 보여주는 메시지 확인
|
// AppLayout.tsx에서 profileRole !== 'super_admin'일 때 보여주는 메시지 확인
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(/접근 권한이 없습니다|이 작업을 수행할 권한이 없습니다/i),
|
page.getByText(
|
||||||
|
/접근 권한이 없습니다|이 작업을 수행할 권한이 없습니다/i,
|
||||||
|
),
|
||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -440,7 +440,6 @@ test.describe("Tenants Management", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.skip("should create a new tenant", async ({ page }) => {
|
test.skip("should create a new tenant", async ({ page }) => {
|
||||||
|
|
||||||
await page.goto("/tenants/new");
|
await page.goto("/tenants/new");
|
||||||
await expect(page.locator("h2").last()).toContainText(/추가|Create/i, {
|
await expect(page.locator("h2").last()).toContainText(/추가|Create/i, {
|
||||||
timeout: 20000,
|
timeout: 20000,
|
||||||
|
|||||||
@@ -228,12 +228,7 @@ test.describe("Worksmobile tenant management", () => {
|
|||||||
return route.fulfill({ json: { items: [], total: 0 }, headers });
|
return route.fulfill({ json: { items: [], total: 0 }, headers });
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.goto("/");
|
|
||||||
await expect(
|
|
||||||
page.getByRole("link", { name: "Worksmobile" }),
|
|
||||||
).toHaveAttribute("href", "/worksmobile");
|
|
||||||
await page.goto("/worksmobile");
|
await page.goto("/worksmobile");
|
||||||
|
|
||||||
await expect(page).toHaveURL(/\/worksmobile$/);
|
await expect(page).toHaveURL(/\/worksmobile$/);
|
||||||
await expect(page.getByRole("tab", { name: "이력" })).toBeVisible();
|
await expect(page.getByRole("tab", { name: "이력" })).toBeVisible();
|
||||||
await expect(page.getByRole("tab", { name: "사용자" })).toBeVisible();
|
await expect(page.getByRole("tab", { name: "사용자" })).toBeVisible();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { DeveloperAccessRequestCard } from "./DeveloperAccessRequestCard";
|
import { DeveloperAccessRequestCard } from "./DeveloperAccessRequestCard";
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,16 @@ export function ForbiddenMessage({ resourceToken }: Props) {
|
|||||||
"Standard user accounts can use this feature only when an operational or administrative relationship is granted for the target application. Request access from an administrator if needed.",
|
"Standard user accounts can use this feature only when an operational or administrative relationship is granted for the target application. Request access from an administrator if needed.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else if (role === "rp_admin") {
|
||||||
|
explanation = t(
|
||||||
|
"msg.dev.forbidden.rp_admin",
|
||||||
|
"RP administrators can only access resources for their assigned applications.",
|
||||||
|
);
|
||||||
|
} else if (role === "tenant_admin") {
|
||||||
|
explanation = t(
|
||||||
|
"msg.dev.forbidden.tenant_admin",
|
||||||
|
"Tenant administrator permissions are not configured correctly or have expired.",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceLabel =
|
const resourceLabel =
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import AuditLogsPage from "./AuditLogsPage";
|
import AuditLogsPage from "./AuditLogsPage";
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import { parseAuditDetails } from "../../../../common/core/audit";
|
|||||||
import { AuditLogTable } from "../../../../common/core/components/audit";
|
import { AuditLogTable } from "../../../../common/core/components/audit";
|
||||||
import { PageHeader } from "../../../../common/core/components/page";
|
import { PageHeader } from "../../../../common/core/components/page";
|
||||||
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
|
import { SearchFilterBar } from "../../../../common/ui/search-filter-bar";
|
||||||
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
|
||||||
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
||||||
import { useDeveloperAccessGate } from "../developer-access/developerAccessGate";
|
import { ForbiddenMessage } from "../../components/common/ForbiddenMessage";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +25,7 @@ import { fetchDevAuditLogs } from "../../lib/devApi";
|
|||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { resolveProfileRole } from "../../lib/role";
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
import { fetchMe } from "../auth/authApi";
|
import { fetchMe } from "../auth/authApi";
|
||||||
|
import { useDeveloperAccessGate } from "../developer-access/developerAccessGate";
|
||||||
|
|
||||||
function toCsv(logs: DevAuditLog[]) {
|
function toCsv(logs: DevAuditLog[]) {
|
||||||
const header = [
|
const header = [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import ClientsPage from "./ClientsPage";
|
import ClientsPage from "./ClientsPage";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
import type { ComponentProps, ReactNode } from "react";
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
import type { ReactNode, ComponentProps } from "react";
|
import { act } from "react-dom/test-utils";
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||||
import { ClientLogo } from "./ClientLogo";
|
import { ClientLogo } from "./ClientLogo";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { ClientFederationPage } from "./ClientFederationPage";
|
import { ClientFederationPage } from "./ClientFederationPage";
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
fetchDeveloperRequestStatus,
|
|
||||||
type DeveloperRequestStatus,
|
type DeveloperRequestStatus,
|
||||||
|
fetchDeveloperRequestStatus,
|
||||||
} from "../../lib/devApi";
|
} from "../../lib/devApi";
|
||||||
|
|
||||||
export type DeveloperAccessGateState = {
|
export type DeveloperAccessGateState = {
|
||||||
@@ -12,7 +12,11 @@ export type DeveloperAccessGateState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function isPrivilegedDeveloperRole(profileRole: string) {
|
function isPrivilegedDeveloperRole(profileRole: string) {
|
||||||
return profileRole === "super_admin";
|
return (
|
||||||
|
profileRole === "super_admin" ||
|
||||||
|
profileRole === "rp_admin" ||
|
||||||
|
profileRole === "tenant_admin"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveDeveloperAccessGate(
|
export function resolveDeveloperAccessGate(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { act } from "react-dom/test-utils";
|
|
||||||
import { createRoot, type Root } from "react-dom/client";
|
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import { createRoot, type Root } from "react-dom/client";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import DeveloperRequestPage from "./DeveloperRequestPage";
|
import DeveloperRequestPage from "./DeveloperRequestPage";
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import {
|
|||||||
Activity,
|
Activity,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Clock3,
|
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
|
Clock3,
|
||||||
Layers3,
|
Layers3,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
@@ -21,7 +21,6 @@ import {
|
|||||||
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
import { DeveloperAccessRequestCard } from "../../components/common/DeveloperAccessRequestCard";
|
||||||
import { Badge } from "../../components/ui/badge";
|
import { Badge } from "../../components/ui/badge";
|
||||||
import { Button } from "../../components/ui/button";
|
import { Button } from "../../components/ui/button";
|
||||||
import { useDeveloperAccessGate } from "../developer-access/developerAccessGate";
|
|
||||||
import {
|
import {
|
||||||
type ClientSummary,
|
type ClientSummary,
|
||||||
fetchClients,
|
fetchClients,
|
||||||
@@ -35,6 +34,7 @@ import {
|
|||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
import { resolveProfileRole } from "../../lib/role";
|
import { resolveProfileRole } from "../../lib/role";
|
||||||
import { fetchMe } from "../auth/authApi";
|
import { fetchMe } from "../auth/authApi";
|
||||||
|
import { useDeveloperAccessGate } from "../developer-access/developerAccessGate";
|
||||||
import {
|
import {
|
||||||
buildRecentClientChanges,
|
buildRecentClientChanges,
|
||||||
type RecentClientChange,
|
type RecentClientChange,
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import {
|
import {
|
||||||
|
type AuditDetails,
|
||||||
|
type CommonAuditLog,
|
||||||
formatAuditValue,
|
formatAuditValue,
|
||||||
parseAuditDetails,
|
parseAuditDetails,
|
||||||
resolveAuditActor,
|
resolveAuditActor,
|
||||||
type AuditDetails,
|
|
||||||
type CommonAuditLog,
|
|
||||||
} from "../../../../common/core/audit";
|
} from "../../../../common/core/audit";
|
||||||
import { t } from "../../lib/i18n";
|
|
||||||
import type { ClientSummary, DevAuditLog } from "../../lib/devApi";
|
import type { ClientSummary, DevAuditLog } from "../../lib/devApi";
|
||||||
|
import { t } from "../../lib/i18n";
|
||||||
|
|
||||||
export type RecentClientChange = {
|
export type RecentClientChange = {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
|
|||||||
@@ -7,6 +7,14 @@ export function normalizeRole(rawRole: unknown): string {
|
|||||||
case "superadmin":
|
case "superadmin":
|
||||||
case "super-admin":
|
case "super-admin":
|
||||||
return "super_admin";
|
return "super_admin";
|
||||||
|
case "rp_admin":
|
||||||
|
case "rpadmin":
|
||||||
|
case "rp-admin":
|
||||||
|
return "rp_admin";
|
||||||
|
case "tenant_admin":
|
||||||
|
case "tenantadmin":
|
||||||
|
case "tenant-admin":
|
||||||
|
return "tenant_admin";
|
||||||
default:
|
default:
|
||||||
return "user";
|
return "user";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
import {
|
import {
|
||||||
type DevAssignableUser,
|
|
||||||
type AuditLog,
|
type AuditLog,
|
||||||
type Consent,
|
type Consent,
|
||||||
|
type DevAssignableUser,
|
||||||
installDevApiMock,
|
installDevApiMock,
|
||||||
makeClient,
|
makeClient,
|
||||||
seedAuth,
|
seedAuth,
|
||||||
|
|||||||
Reference in New Issue
Block a user