forked from baron/baron-sso
chore: fix biome lint warnings & update Makefile
This commit is contained in:
12
Makefile
12
Makefile
@@ -276,18 +276,18 @@ code-check-front-lint:
|
|||||||
@echo "==> adminfront biome lint/format check"
|
@echo "==> adminfront biome lint/format check"
|
||||||
rm -rf adminfront/playwright-report adminfront/test-results
|
rm -rf adminfront/playwright-report adminfront/test-results
|
||||||
cd adminfront && CI=true npx pnpm install --frozen-lockfile --ignore-scripts
|
cd adminfront && CI=true npx pnpm install --frozen-lockfile --ignore-scripts
|
||||||
cd adminfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
cd adminfront && npx biome lint .
|
||||||
cd adminfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
cd adminfront && npx biome format .
|
||||||
@echo "==> devfront biome lint/format check"
|
@echo "==> devfront biome lint/format check"
|
||||||
rm -rf devfront/playwright-report devfront/test-results
|
rm -rf devfront/playwright-report devfront/test-results
|
||||||
cd devfront && npm ci --ignore-scripts
|
cd devfront && npm ci --ignore-scripts
|
||||||
cd devfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
cd devfront && npx biome lint .
|
||||||
cd devfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
cd devfront && npx biome format .
|
||||||
@echo "==> orgfront biome lint/format check"
|
@echo "==> orgfront biome lint/format check"
|
||||||
rm -rf orgfront/playwright-report orgfront/test-results
|
rm -rf orgfront/playwright-report orgfront/test-results
|
||||||
cd orgfront && npm ci --ignore-scripts
|
cd orgfront && npm ci --ignore-scripts
|
||||||
cd orgfront && npx biome check . --formatter-enabled=false --organize-imports-enabled=false
|
cd orgfront && npx biome lint .
|
||||||
cd orgfront && npx biome check . --linter-enabled=false --organize-imports-enabled=false
|
cd orgfront && npx biome format .
|
||||||
|
|
||||||
code-check-backend-tests:
|
code-check-backend-tests:
|
||||||
@echo "==> backend tests"
|
@echo "==> backend tests"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createServer } from "node:http";
|
|||||||
import { extname, join, normalize, resolve } from "node:path";
|
import { extname, join, normalize, resolve } from "node:path";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
const rootDir = fileURLToPath(new URL("..", import.meta.url));
|
const _rootDir = fileURLToPath(new URL("..", import.meta.url));
|
||||||
const distDir = resolve(
|
const distDir = resolve(
|
||||||
process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist",
|
process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist",
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import { TenantProfilePage } from "../features/tenants/routes/TenantProfilePage"
|
|||||||
import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage";
|
import { TenantSchemaPage } from "../features/tenants/routes/TenantSchemaPage";
|
||||||
import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage";
|
import { TenantWorksmobilePage } from "../features/tenants/routes/TenantWorksmobilePage";
|
||||||
import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab";
|
import TenantUserGroupsTab from "../features/user-groups/routes/TenantUserGroupsTab";
|
||||||
import { UserGroupDetailPage } from "../features/user-groups/routes/UserGroupDetailPage";
|
|
||||||
import UserCreatePage from "../features/users/UserCreatePage";
|
import UserCreatePage from "../features/users/UserCreatePage";
|
||||||
import UserDetailPage from "../features/users/UserDetailPage";
|
import UserDetailPage from "../features/users/UserDetailPage";
|
||||||
import UserListPage from "../features/users/UserListPage";
|
import UserListPage from "../features/users/UserListPage";
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import type { AxiosError } from "axios";
|
|||||||
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
|
import { Download, NotebookTabs, RefreshCw, Search } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
formatAuditValue,
|
|
||||||
parseAuditDetails,
|
parseAuditDetails,
|
||||||
resolveAuditAction,
|
resolveAuditAction,
|
||||||
resolveAuditActor,
|
resolveAuditActor,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import {
|
|||||||
fetchDataIntegrityReport,
|
fetchDataIntegrityReport,
|
||||||
type RPUsageDailyMetric,
|
type RPUsageDailyMetric,
|
||||||
type RPUsagePeriod,
|
type RPUsagePeriod,
|
||||||
type TenantSummary,
|
|
||||||
} from "../../lib/adminApi";
|
} from "../../lib/adminApi";
|
||||||
import { t } from "../../lib/i18n";
|
import { t } from "../../lib/i18n";
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
Trash2,
|
|
||||||
UserPlus,
|
UserPlus,
|
||||||
Users,
|
Users,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
@@ -28,7 +27,6 @@ import {
|
|||||||
DialogDescription,
|
DialogDescription,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
import {
|
import {
|
||||||
@@ -68,7 +66,7 @@ function mergePendingMembers(
|
|||||||
export function TenantAdminsAndOwnersTab() {
|
export function TenantAdminsAndOwnersTab() {
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const currentUserId = auth.user?.profile.sub;
|
const _currentUserId = auth.user?.profile.sub;
|
||||||
const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>();
|
const { tenantId: tenantIdParam } = useParams<{ tenantId: string }>();
|
||||||
const tenantId = tenantIdParam ?? "";
|
const tenantId = tenantIdParam ?? "";
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -187,7 +185,7 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (err: AxiosError<{ error?: string }>, userId, context) => {
|
onError: (err: AxiosError<{ error?: string }>, _userId, context) => {
|
||||||
if (context?.previousOwners) {
|
if (context?.previousOwners) {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
["tenant-owners", tenantId],
|
["tenant-owners", tenantId],
|
||||||
@@ -288,7 +286,7 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
t("msg.admin.tenants.admins.remove_success", "권한이 회수되었습니다."),
|
t("msg.admin.tenants.admins.remove_success", "권한이 회수되었습니다."),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onError: (err: AxiosError<{ error?: string }>, userId, context) => {
|
onError: (err: AxiosError<{ error?: string }>, _userId, context) => {
|
||||||
if (context?.previousAdmins) {
|
if (context?.previousAdmins) {
|
||||||
queryClient.setQueryData(
|
queryClient.setQueryData(
|
||||||
["tenant-admins", tenantId],
|
["tenant-admins", tenantId],
|
||||||
@@ -310,7 +308,7 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveOwner = (userId: string, userName: string) => {
|
const _handleRemoveOwner = (userId: string, userName: string) => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(
|
||||||
t(
|
t(
|
||||||
@@ -324,7 +322,7 @@ export function TenantAdminsAndOwnersTab() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveAdmin = (userId: string, userName: string) => {
|
const _handleRemoveAdmin = (userId: string, userName: string) => {
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(
|
||||||
t(
|
t(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Copy } from "lucide-react";
|
import { Copy } from "lucide-react";
|
||||||
import { Link, Outlet, useLocation, useParams } from "react-router-dom";
|
import { Link, Outlet, useLocation, useParams } from "react-router-dom";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
|
||||||
import { Button } from "../../../components/ui/button";
|
import { Button } from "../../../components/ui/button";
|
||||||
import { fetchMe, fetchTenant } from "../../../lib/adminApi";
|
import { fetchMe, fetchTenant } from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
import { Label } from "../../../components/ui/label";
|
import { Label } from "../../../components/ui/label";
|
||||||
@@ -239,7 +238,7 @@ const UserGroupTreeNode: React.FC<UserGroupTreeNodeProps> = ({
|
|||||||
function TenantGroupsPage() {
|
function TenantGroupsPage() {
|
||||||
const params = useParams<{ tenantId: string }>();
|
const params = useParams<{ tenantId: string }>();
|
||||||
const tenantId = params.tenantId ?? "";
|
const tenantId = params.tenantId ?? "";
|
||||||
const queryClient = useQueryClient();
|
const _queryClient = useQueryClient();
|
||||||
|
|
||||||
const [newGroupName, setNewGroupName] = useState("");
|
const [newGroupName, setNewGroupName] = useState("");
|
||||||
const [newGroupDesc, setNewGroupNameDesc] = useState("");
|
const [newGroupDesc, setNewGroupNameDesc] = useState("");
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import {
|
import {
|
||||||
ArrowDown,
|
ArrowDown,
|
||||||
@@ -14,7 +13,6 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Download,
|
Download,
|
||||||
ExternalLink,
|
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
List,
|
List,
|
||||||
@@ -28,22 +26,13 @@ import {
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { PageHeader } from "../../../../../common/core/components/page";
|
import { PageHeader } from "../../../../../common/core/components/page";
|
||||||
import {
|
|
||||||
SortableTableHead,
|
|
||||||
sortableTableHeadBaseClassName,
|
|
||||||
sortableTableHeaderClassName,
|
|
||||||
} from "../../../../../common/core/components/sort";
|
|
||||||
import {
|
import {
|
||||||
type SortConfig,
|
type SortConfig,
|
||||||
type SortResolverMap,
|
type SortResolverMap,
|
||||||
sortItems,
|
sortItems,
|
||||||
toggleSort,
|
toggleSort,
|
||||||
} from "../../../../../common/core/utils";
|
} from "../../../../../common/core/utils";
|
||||||
import {
|
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
||||||
commonStickyTableHeaderClass,
|
|
||||||
commonTableShellClass,
|
|
||||||
commonTableViewportClass,
|
|
||||||
} from "../../../../../common/ui/table";
|
|
||||||
import { RoleGuard } from "../../../components/auth/RoleGuard";
|
import { RoleGuard } from "../../../components/auth/RoleGuard";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
import { Button } from "../../../components/ui/button";
|
import { Button } from "../../../components/ui/button";
|
||||||
@@ -71,7 +60,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../../../components/ui/dropdown-menu";
|
} from "../../../components/ui/dropdown-menu";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
import { ScrollArea } from "../../../components/ui/scroll-area";
|
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
@@ -79,7 +67,6 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "../../../components/ui/select";
|
} from "../../../components/ui/select";
|
||||||
import { Separator } from "../../../components/ui/separator";
|
|
||||||
import { Switch } from "../../../components/ui/switch";
|
import { Switch } from "../../../components/ui/switch";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -132,10 +119,10 @@ import {
|
|||||||
const tenantCSVTemplate =
|
const tenantCSVTemplate =
|
||||||
"name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type\n";
|
"name,type,parent_tenant_slug,slug,memo,email_domain,visibility,org_unit_type\n";
|
||||||
const tenantPageSize = 500;
|
const tenantPageSize = 500;
|
||||||
const tenantVirtualizationThreshold = 250;
|
const _tenantVirtualizationThreshold = 250;
|
||||||
const tenantEstimatedRowHeight = 73;
|
const _tenantEstimatedRowHeight = 73;
|
||||||
const tenantLoadAheadPx = 360;
|
const _tenantLoadAheadPx = 360;
|
||||||
const tenantLoadAheadRows = 30;
|
const _tenantLoadAheadRows = 30;
|
||||||
|
|
||||||
type TenantSortKey = keyof TenantSummary | "recursiveMemberCount";
|
type TenantSortKey = keyof TenantSummary | "recursiveMemberCount";
|
||||||
type TenantListRow = TenantSummary & { recursiveMemberCount: number };
|
type TenantListRow = TenantSummary & { recursiveMemberCount: number };
|
||||||
@@ -301,7 +288,7 @@ function TenantListPage() {
|
|||||||
>({});
|
>({});
|
||||||
const [previewOpen, setPreviewOpen] = React.useState(false);
|
const [previewOpen, setPreviewOpen] = React.useState(false);
|
||||||
const [selectedBulkStatus, setSelectedBulkStatus] = React.useState("");
|
const [selectedBulkStatus, setSelectedBulkStatus] = React.useState("");
|
||||||
const tenantTableScrollRef = React.useRef<HTMLDivElement | null>(null);
|
const _tenantTableScrollRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const { data: profile } = useQuery({
|
const { data: profile } = useQuery({
|
||||||
queryKey: ["me"],
|
queryKey: ["me"],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { ArrowRight, Building2, Plus } from "lucide-react";
|
import { Building2, Plus } from "lucide-react";
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import {
|
import {
|
||||||
commonStickyTableHeaderClass,
|
commonStickyTableHeaderClass,
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import type { AxiosError } from "axios";
|
import type { AxiosError } from "axios";
|
||||||
import {
|
import { Loader2, Mail, Plus, User, UserPlus } from "lucide-react";
|
||||||
Loader2,
|
|
||||||
Mail,
|
|
||||||
MoreHorizontal,
|
|
||||||
Plus,
|
|
||||||
User,
|
|
||||||
UserMinus,
|
|
||||||
UserPlus,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
@@ -19,12 +11,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../../components/ui/card";
|
} from "../../../components/ui/card";
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "../../../components/ui/dropdown-menu";
|
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableBody,
|
TableBody,
|
||||||
@@ -80,7 +66,7 @@ function TenantUsersPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRemoveMember = (userId: string, userName: string) => {
|
const _handleRemoveMember = (userId: string, userName: string) => {
|
||||||
if (!tenantSlug) return;
|
if (!tenantSlug) return;
|
||||||
if (
|
if (
|
||||||
window.confirm(
|
window.confirm(
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { Building2, Plus, Users } from "lucide-react";
|
import { Building2, Plus, Users } from "lucide-react";
|
||||||
import { useState } from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
import { commonStickyTableHeaderClass } from "../../../../../common/ui/table";
|
||||||
import { Badge } from "../../../components/ui/badge";
|
import { Badge } from "../../../components/ui/badge";
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
Building2,
|
Building2,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
CornerDownRight,
|
|
||||||
Download,
|
Download,
|
||||||
ExternalLink,
|
ExternalLink,
|
||||||
FolderOpen,
|
FolderOpen,
|
||||||
@@ -41,7 +40,6 @@ import {
|
|||||||
DialogFooter,
|
DialogFooter,
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "../../../components/ui/dialog";
|
} from "../../../components/ui/dialog";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -52,7 +50,6 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "../../../components/ui/dropdown-menu";
|
} from "../../../components/ui/dropdown-menu";
|
||||||
import { Input } from "../../../components/ui/input";
|
import { Input } from "../../../components/ui/input";
|
||||||
import { Label } from "../../../components/ui/label";
|
|
||||||
import { ScrollArea } from "../../../components/ui/scroll-area";
|
import { ScrollArea } from "../../../components/ui/scroll-area";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
@@ -62,15 +59,8 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "../../../components/ui/table";
|
} from "../../../components/ui/table";
|
||||||
import {
|
|
||||||
Tabs,
|
|
||||||
TabsContent,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
} from "../../../components/ui/tabs";
|
|
||||||
import { toast } from "../../../components/ui/use-toast";
|
import { toast } from "../../../components/ui/use-toast";
|
||||||
import {
|
import {
|
||||||
createUser,
|
|
||||||
exportTenantsCSV,
|
exportTenantsCSV,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchUsers,
|
fetchUsers,
|
||||||
@@ -413,7 +403,7 @@ const MemberTable: React.FC<{
|
|||||||
|
|
||||||
function TenantUserGroupsTab() {
|
function TenantUserGroupsTab() {
|
||||||
const { tenantId } = useParams<{ tenantId: string }>();
|
const { tenantId } = useParams<{ tenantId: string }>();
|
||||||
const navigate = useNavigate();
|
const _navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [selectedNodeId, setSelectedNodeId] = useState<string>(tenantId || "");
|
const [selectedNodeId, setSelectedNodeId] = useState<string>(tenantId || "");
|
||||||
@@ -855,7 +845,7 @@ const UserAddDialog: React.FC<{
|
|||||||
try {
|
try {
|
||||||
const res = await fetchUsers(20, 0, userSearch);
|
const res = await fetchUsers(20, 0, userSearch);
|
||||||
setSearchResults(res.items);
|
setSearchResults(res.items);
|
||||||
} catch (err) {
|
} catch (_err) {
|
||||||
toast.error(t("msg.admin.users.list.fetch_error", "사용자 검색 실패"));
|
toast.error(t("msg.admin.users.list.fetch_error", "사용자 검색 실패"));
|
||||||
} finally {
|
} finally {
|
||||||
setIsSearching(false);
|
setIsSearching(false);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
Save,
|
Save,
|
||||||
Trash2,
|
Trash2,
|
||||||
Mail,
|
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -22,7 +21,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../components/ui/card";
|
} from "../../components/ui/card";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -184,7 +182,7 @@ function UserCreatePage() {
|
|||||||
if (e.key === "Enter" || e.key === "," || e.key === " ") {
|
if (e.key === "Enter" || e.key === "," || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const value = newSubEmail.trim().replace(/,/g, "");
|
const value = newSubEmail.trim().replace(/,/g, "");
|
||||||
if (value && value.includes("@") && !currentSubEmails.includes(value)) {
|
if (value?.includes("@") && !currentSubEmails.includes(value)) {
|
||||||
setValue("metadata.sub_email", [...currentSubEmails, value], {
|
setValue("metadata.sub_email", [...currentSubEmails, value], {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
});
|
});
|
||||||
@@ -667,8 +665,7 @@ function UserCreatePage() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const value = newSubEmail.trim().replace(/,/g, "");
|
const value = newSubEmail.trim().replace(/,/g, "");
|
||||||
if (
|
if (
|
||||||
value &&
|
value?.includes("@") &&
|
||||||
value.includes("@") &&
|
|
||||||
!currentSubEmails.includes(value)
|
!currentSubEmails.includes(value)
|
||||||
) {
|
) {
|
||||||
setValue(
|
setValue(
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "../../components/ui/card";
|
} from "../../components/ui/card";
|
||||||
import { Checkbox } from "../../components/ui/checkbox";
|
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -95,7 +94,10 @@ import { resolvePersonalTenant } from "./utils/personalTenant";
|
|||||||
|
|
||||||
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
|
type UserFormValues = Omit<UserUpdateRequest, "metadata"> & {
|
||||||
email: string;
|
email: string;
|
||||||
metadata: Record<string, Record<string, string | number | boolean | string[]> | string[] | undefined> & {
|
metadata: Record<
|
||||||
|
string,
|
||||||
|
Record<string, string | number | boolean | string[]> | string[] | undefined
|
||||||
|
> & {
|
||||||
sub_email?: string[];
|
sub_email?: string[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -322,8 +324,8 @@ function UserDetailPage() {
|
|||||||
const userId = params.id ?? "";
|
const userId = params.id ?? "";
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [_error, _setError] = React.useState<string | null>(null);
|
||||||
const [successMsg, setSuccessMsg] = React.useState<string | null>(null);
|
const [_successMsg, _setSuccessMsg] = React.useState<string | null>(null);
|
||||||
const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false);
|
const [isPasswordResetOpen, setIsPasswordResetOpen] = React.useState(false);
|
||||||
const [generatedPassword, setGeneratedPassword] = React.useState<
|
const [generatedPassword, setGeneratedPassword] = React.useState<
|
||||||
string | null
|
string | null
|
||||||
@@ -419,7 +421,7 @@ function UserDetailPage() {
|
|||||||
if (e.key === "Enter" || e.key === "," || e.key === " ") {
|
if (e.key === "Enter" || e.key === "," || e.key === " ") {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const value = newSubEmail.trim().replace(/,/g, "");
|
const value = newSubEmail.trim().replace(/,/g, "");
|
||||||
if (value && value.includes("@") && !currentSubEmails.includes(value)) {
|
if (value?.includes("@") && !currentSubEmails.includes(value)) {
|
||||||
setValue("metadata.sub_email", [...currentSubEmails, value], {
|
setValue("metadata.sub_email", [...currentSubEmails, value], {
|
||||||
shouldDirty: true,
|
shouldDirty: true,
|
||||||
});
|
});
|
||||||
@@ -595,7 +597,7 @@ function UserDetailPage() {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const setPrimaryAppointment = (targetIndex: number) => {
|
const _setPrimaryAppointment = (targetIndex: number) => {
|
||||||
setAdditionalAppointments((current) =>
|
setAdditionalAppointments((current) =>
|
||||||
current.map((appointment, index) => ({
|
current.map((appointment, index) => ({
|
||||||
...appointment,
|
...appointment,
|
||||||
@@ -793,11 +795,13 @@ function UserDetailPage() {
|
|||||||
sub_email: rawSubEmail,
|
sub_email: rawSubEmail,
|
||||||
...safeMetadata
|
...safeMetadata
|
||||||
} = cleanMetadata;
|
} = cleanMetadata;
|
||||||
|
|
||||||
// Parse sub_email
|
// Parse sub_email
|
||||||
let sub_email: string[] = [];
|
let sub_email: string[] = [];
|
||||||
if (Array.isArray(rawSubEmail)) {
|
if (Array.isArray(rawSubEmail)) {
|
||||||
sub_email = rawSubEmail.filter(e => typeof e === 'string' && e.includes("@"));
|
sub_email = rawSubEmail.filter(
|
||||||
|
(e) => typeof e === "string" && e.includes("@"),
|
||||||
|
);
|
||||||
} else if (typeof rawSubEmail === "string" && rawSubEmail.trim() !== "") {
|
} else if (typeof rawSubEmail === "string" && rawSubEmail.trim() !== "") {
|
||||||
sub_email = rawSubEmail
|
sub_email = rawSubEmail
|
||||||
.split(/[;,\n\r\t]/)
|
.split(/[;,\n\r\t]/)
|
||||||
@@ -816,7 +820,7 @@ function UserDetailPage() {
|
|||||||
};
|
};
|
||||||
// email cannot be updated directly via this API in current backend implementation,
|
// email cannot be updated directly via this API in current backend implementation,
|
||||||
// so we delete it from payload if it spread
|
// so we delete it from payload if it spread
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
delete payload.email;
|
delete payload.email;
|
||||||
payload.role = undefined;
|
payload.role = undefined;
|
||||||
|
|
||||||
@@ -992,16 +996,21 @@ function UserDetailPage() {
|
|||||||
<Mail size={14} className="text-primary/70" />
|
<Mail size={14} className="text-primary/70" />
|
||||||
{user.email}
|
{user.email}
|
||||||
</div>
|
</div>
|
||||||
{Boolean(user.metadata?.sub_email &&
|
{Boolean(
|
||||||
Array.isArray(user.metadata.sub_email) &&
|
user.metadata?.sub_email &&
|
||||||
user.metadata.sub_email.length > 0) && (
|
Array.isArray(user.metadata.sub_email) &&
|
||||||
<div className="flex items-center gap-1.5 bg-background px-2.5 py-1 rounded-full border">
|
user.metadata.sub_email.length > 0,
|
||||||
<Mail size={14} className="text-primary/40" />
|
) && (
|
||||||
<span className="text-[10px] font-bold">
|
<div className="flex items-center gap-1.5 bg-background px-2.5 py-1 rounded-full border">
|
||||||
+{Array.isArray(user.metadata?.sub_email) ? user.metadata.sub_email.length : 0}
|
<Mail size={14} className="text-primary/40" />
|
||||||
</span>
|
<span className="text-[10px] font-bold">
|
||||||
</div>
|
+
|
||||||
)}
|
{Array.isArray(user.metadata?.sub_email)
|
||||||
|
? user.metadata.sub_email.length
|
||||||
|
: 0}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{user.phone && (
|
{user.phone && (
|
||||||
<div className="flex items-center gap-1.5 bg-background px-2.5 py-1 rounded-full border">
|
<div className="flex items-center gap-1.5 bg-background px-2.5 py-1 rounded-full border">
|
||||||
<Shield size={14} className="text-primary/70" />
|
<Shield size={14} className="text-primary/70" />
|
||||||
@@ -1170,8 +1179,7 @@ function UserDetailPage() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
const value = newSubEmail.trim().replace(/,/g, "");
|
const value = newSubEmail.trim().replace(/,/g, "");
|
||||||
if (
|
if (
|
||||||
value &&
|
value?.includes("@") &&
|
||||||
value.includes("@") &&
|
|
||||||
!currentSubEmails.includes(value)
|
!currentSubEmails.includes(value)
|
||||||
) {
|
) {
|
||||||
setValue(
|
setValue(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
|
import { fireEvent, render, screen } from "@testing-library/react";
|
||||||
import { MemoryRouter } from "react-router-dom";
|
import { MemoryRouter } from "react-router-dom";
|
||||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { createI18nMock } from "../../test/i18nMock";
|
import { createI18nMock } from "../../test/i18nMock";
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import {
|
|||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
Download,
|
|
||||||
FileDown,
|
FileDown,
|
||||||
FileSpreadsheet,
|
FileSpreadsheet,
|
||||||
LayoutDashboard,
|
LayoutDashboard,
|
||||||
@@ -268,7 +267,7 @@ const UserListSearchControls = React.memo(function UserListSearchControls({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function UserListPage() {
|
function UserListPage() {
|
||||||
const navigate = useNavigate();
|
const _navigate = useNavigate();
|
||||||
const [page, setPage] = React.useState(1);
|
const [page, setPage] = React.useState(1);
|
||||||
const [search, setSearch] = React.useState("");
|
const [search, setSearch] = React.useState("");
|
||||||
const [selectedCompany, setSelectedCompany] = React.useState<string>("");
|
const [selectedCompany, setSelectedCompany] = React.useState<string>("");
|
||||||
@@ -563,7 +562,7 @@ function UserListPage() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleApplyBulkStatus = () => {
|
const _handleApplyBulkStatus = () => {
|
||||||
if (selectedUserIds.length === 0 || !selectedBulkStatus) return;
|
if (selectedUserIds.length === 0 || !selectedBulkStatus) return;
|
||||||
bulkUpdateMutation.mutate({
|
bulkUpdateMutation.mutate({
|
||||||
userIds: selectedUserIds,
|
userIds: selectedUserIds,
|
||||||
@@ -571,7 +570,7 @@ function UserListPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleApplyBulkPermission = () => {
|
const _handleApplyBulkPermission = () => {
|
||||||
if (selectedUserIds.length === 0 || !selectedBulkPermission) return;
|
if (selectedUserIds.length === 0 || !selectedBulkPermission) return;
|
||||||
bulkUpdateMutation.mutate({
|
bulkUpdateMutation.mutate({
|
||||||
userIds: selectedUserIds,
|
userIds: selectedUserIds,
|
||||||
@@ -594,7 +593,7 @@ function UserListPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (userId: string, userName: string) => {
|
const _handleDelete = (userId: string, userName: string) => {
|
||||||
if (
|
if (
|
||||||
!window.confirm(
|
!window.confirm(
|
||||||
t(
|
t(
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import {
|
|||||||
bulkUpdateUsers,
|
bulkUpdateUsers,
|
||||||
fetchAllTenants,
|
fetchAllTenants,
|
||||||
fetchGroups,
|
fetchGroups,
|
||||||
type GroupSummary,
|
|
||||||
type TenantSummary,
|
|
||||||
type UserSummary,
|
type UserSummary,
|
||||||
} from "../../../lib/adminApi";
|
} from "../../../lib/adminApi";
|
||||||
import { t } from "../../../lib/i18n";
|
import { t } from "../../../lib/i18n";
|
||||||
@@ -49,7 +47,7 @@ export function UserBulkMoveGroupModal({
|
|||||||
const [searchTerm, setSearchTerm] = React.useState("");
|
const [searchTerm, setSearchTerm] = React.useState("");
|
||||||
const [acknowledgeWarning, setAcknowledgeWarning] = React.useState(false);
|
const [acknowledgeWarning, setAcknowledgeWarning] = React.useState(false);
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const _queryClient = useQueryClient();
|
||||||
|
|
||||||
const { data: tenantsData } = useQuery({
|
const { data: tenantsData } = useQuery({
|
||||||
queryKey: ["tenants", "all"],
|
queryKey: ["tenants", "all"],
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export function isHanmacFamilyTenant<T extends TenantFilterTarget>(
|
|||||||
tenants: T[],
|
tenants: T[],
|
||||||
hanmacFamilyTenantId?: string,
|
hanmacFamilyTenantId?: string,
|
||||||
) {
|
) {
|
||||||
if (!tenant || !tenant.id) return false;
|
if (!tenant?.id) return false;
|
||||||
|
|
||||||
const rootTenantId = resolveHanmacFamilyTenantId(
|
const rootTenantId = resolveHanmacFamilyTenantId(
|
||||||
tenants,
|
tenants,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export function buildTenantFullTree(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const visitedDuringBuild = new Set<string>();
|
const _visitedDuringBuild = new Set<string>();
|
||||||
// Build initial children relations and prevent simple cycles
|
// Build initial children relations and prevent simple cycles
|
||||||
for (const t of allTenants) {
|
for (const t of allTenants) {
|
||||||
if (t.parentId && t.parentId !== t.id) {
|
if (t.parentId && t.parentId !== t.id) {
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ test.describe("Tenants Management", () => {
|
|||||||
page,
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
await page.setViewportSize({ width: 900, height: 700 });
|
await page.setViewportSize({ width: 900, height: 700 });
|
||||||
let requestCount = 0;
|
let _requestCount = 0;
|
||||||
|
|
||||||
await page.route("**/api/v1/admin/tenants**", async (route) => {
|
await page.route("**/api/v1/admin/tenants**", async (route) => {
|
||||||
if (route.request().method() !== "GET") {
|
if (route.request().method() !== "GET") {
|
||||||
@@ -266,7 +266,7 @@ test.describe("Tenants Management", () => {
|
|||||||
}
|
}
|
||||||
const url = new URL(route.request().url());
|
const url = new URL(route.request().url());
|
||||||
const cursor = url.searchParams.get("cursor");
|
const cursor = url.searchParams.get("cursor");
|
||||||
requestCount += 1;
|
_requestCount += 1;
|
||||||
|
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
await page.addInitScript(() => {
|
await page.addInitScript(() => {
|
||||||
window.localStorage.setItem("locale", "ko");
|
window.localStorage.setItem("locale", "ko");
|
||||||
window.localStorage.setItem("admin_session", "fake-token");
|
window.localStorage.setItem("admin_session", "fake-token");
|
||||||
(window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean })._IS_TEST_MODE = true;
|
(
|
||||||
|
window as Window & typeof globalThis & { _IS_TEST_MODE?: boolean }
|
||||||
|
)._IS_TEST_MODE = true;
|
||||||
|
|
||||||
const authData = {
|
const authData = {
|
||||||
access_token: "fake-token",
|
access_token: "fake-token",
|
||||||
@@ -13,12 +15,20 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
profile: { sub: "admin-user", name: "Admin", role: "super_admin" },
|
profile: { sub: "admin-user", name: "Admin", role: "super_admin" },
|
||||||
expires_at: Math.floor(Date.now() / 1000) + 36000,
|
expires_at: Math.floor(Date.now() / 1000) + 36000,
|
||||||
};
|
};
|
||||||
window.localStorage.setItem("oidc.user:http://localhost:5000/oidc:adminfront", JSON.stringify(authData));
|
window.localStorage.setItem(
|
||||||
|
"oidc.user:http://localhost:5000/oidc:adminfront",
|
||||||
|
JSON.stringify(authData),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/v1/user/me", async (route) => {
|
await page.route("**/api/v1/user/me", async (route) => {
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
json: { id: "admin-user", name: "Admin", role: "super_admin", manageableTenants: [] },
|
json: {
|
||||||
|
id: "admin-user",
|
||||||
|
name: "Admin",
|
||||||
|
role: "super_admin",
|
||||||
|
manageableTenants: [],
|
||||||
|
},
|
||||||
headers: { "Access-Control-Allow-Origin": "*" },
|
headers: { "Access-Control-Allow-Origin": "*" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -31,7 +41,7 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.route("**/api/v1/admin/users*", async (route) => {
|
await page.route("**/api/v1/admin/users*", async (route) => {
|
||||||
if(route.request().url().includes("/bulk")) {
|
if (route.request().url().includes("/bulk")) {
|
||||||
return route.continue();
|
return route.continue();
|
||||||
}
|
}
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
@@ -45,14 +55,20 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("should parse secondary_emails and send to backend", async ({ page }) => {
|
test("should parse secondary_emails and send to backend", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
let bulkPayload: any = null;
|
let bulkPayload: any = null;
|
||||||
|
|
||||||
await page.route("**/api/v1/admin/users/bulk", async (route) => {
|
await page.route("**/api/v1/admin/users/bulk", async (route) => {
|
||||||
if (route.request().method() === "POST") {
|
if (route.request().method() === "POST") {
|
||||||
bulkPayload = route.request().postDataJSON();
|
bulkPayload = route.request().postDataJSON();
|
||||||
return route.fulfill({
|
return route.fulfill({
|
||||||
json: { results: [{ email: "test@example.com", success: true, userId: "u-1" }] },
|
json: {
|
||||||
|
results: [
|
||||||
|
{ email: "test@example.com", success: true, userId: "u-1" },
|
||||||
|
],
|
||||||
|
},
|
||||||
headers: { "Access-Control-Allow-Origin": "*" },
|
headers: { "Access-Control-Allow-Origin": "*" },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -60,21 +76,26 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.goto("/users");
|
await page.goto("/users");
|
||||||
await expect(page.getByTestId("page-title")).toContainText(/사용자|Users/i, { timeout: 20000 });
|
await expect(page.getByTestId("page-title")).toContainText(
|
||||||
|
/사용자|Users/i,
|
||||||
|
{ timeout: 20000 },
|
||||||
|
);
|
||||||
|
|
||||||
await page.getByTestId("user-data-mgmt-btn").click();
|
await page.getByTestId("user-data-mgmt-btn").click();
|
||||||
await page.getByRole("menuitem", { name: /일괄 임포트|일괄 등록|Bulk Import/i }).click();
|
await page
|
||||||
|
.getByRole("menuitem", { name: /일괄 임포트|일괄 등록|Bulk Import/i })
|
||||||
|
.click();
|
||||||
|
|
||||||
// Create a mock CSV with secondary_emails
|
// Create a mock CSV with secondary_emails
|
||||||
const csvContent = `email,sub_email,name,phone,role,tenant_slug\ntest@example.com,sub1@test.com;sub2@test.com,홍길동,010-1234-5678,user,tenant-slug`;
|
const csvContent = `email,sub_email,name,phone,role,tenant_slug\ntest@example.com,sub1@test.com;sub2@test.com,홍길동,010-1234-5678,user,tenant-slug`;
|
||||||
|
|
||||||
const fileChooserPromise = page.waitForEvent('filechooser');
|
const fileChooserPromise = page.waitForEvent("filechooser");
|
||||||
await page.getByText(/파일 선택|Change file|Select file/i).click();
|
await page.getByText(/파일 선택|Change file|Select file/i).click();
|
||||||
const fileChooser = await fileChooserPromise;
|
const fileChooser = await fileChooserPromise;
|
||||||
|
|
||||||
await fileChooser.setFiles({
|
await fileChooser.setFiles({
|
||||||
name: 'users_with_secondary.csv',
|
name: "users_with_secondary.csv",
|
||||||
mimeType: 'text/csv',
|
mimeType: "text/csv",
|
||||||
buffer: Buffer.from(csvContent),
|
buffer: Buffer.from(csvContent),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -87,9 +108,9 @@ test.describe("Users Bulk Upload Secondary Emails", () => {
|
|||||||
|
|
||||||
expect(bulkPayload).not.toBeNull();
|
expect(bulkPayload).not.toBeNull();
|
||||||
expect(bulkPayload.users).toHaveLength(1);
|
expect(bulkPayload.users).toHaveLength(1);
|
||||||
|
|
||||||
// The most important check - does it parse to the metadata
|
// The most important check - does it parse to the metadata
|
||||||
expect(bulkPayload.users[0].metadata.sub_email).toContain("sub1@test.com");
|
expect(bulkPayload.users[0].metadata.sub_email).toContain("sub1@test.com");
|
||||||
expect(bulkPayload.users[0].metadata.sub_email).toContain("sub2@test.com");
|
expect(bulkPayload.users[0].metadata.sub_email).toContain("sub2@test.com");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user