feat: Inventar-Export für ERPNext (erp-inventory:read) #1
No files matched your search
@@ -7,6 +7,12 @@ import { ApiKeysTab } from "./api-keys-tab";
|
||||
import { createApiKey, fetchApiKeys, revokeApiKey } from "@/lib/api-keys/api";
|
||||
import type { ApiKey } from "@/lib/api-keys/types";
|
||||
|
||||
// Rechte des angemeldeten Nutzers; je Test anpassbar.
|
||||
let ownPermissions: string[] = [];
|
||||
vi.mock("@/lib/auth/auth-context", () => ({
|
||||
useAuth: () => ({ hasPermission: (permission: string) => ownPermissions.includes(permission) }),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/api-keys/api", () => ({
|
||||
createApiKey: vi.fn(),
|
||||
fetchApiKeys: vi.fn(),
|
||||
@@ -38,6 +44,7 @@ function key(overrides: Partial<ApiKey>): ApiKey {
|
||||
|
||||
describe("ApiKeysTab", () => {
|
||||
beforeEach(() => {
|
||||
ownPermissions = ["api-keys:manage", "shop-sync:read", "shop-sync:write", "erp-inventory:read"];
|
||||
vi.mocked(fetchApiKeys).mockReset().mockResolvedValue([]);
|
||||
vi.mocked(createApiKey).mockReset();
|
||||
vi.mocked(revokeApiKey).mockReset();
|
||||
@@ -94,4 +101,20 @@ describe("ApiKeysTab", () => {
|
||||
expect(await screen.findByText("ERPNext")).toBeInTheDocument();
|
||||
expect(screen.getByText("Darf: Inventar-Spiegel lesen (ERPNext)")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("bietet nur Rechte an, die der angemeldete Nutzer selbst hat", () => {
|
||||
ownPermissions = ["api-keys:manage", "shop-sync:read", "erp-inventory:read"];
|
||||
renderWithClient(<ApiKeysTab />);
|
||||
expect(screen.getByRole("checkbox", { name: /Shop-Daten lesen/ })).toBeInTheDocument();
|
||||
expect(screen.getByRole("checkbox", { name: /Inventar-Spiegel lesen/ })).toBeInTheDocument();
|
||||
expect(screen.queryByRole("checkbox", { name: /Verkäufe und Rückbuchungen/ })).toBeNull();
|
||||
});
|
||||
|
||||
it("wählt nichts vor, was der Nutzer nicht vergeben darf, und erklärt es", () => {
|
||||
ownPermissions = ["api-keys:manage"];
|
||||
renderWithClient(<ApiKeysTab />);
|
||||
expect(screen.queryAllByRole("checkbox")).toHaveLength(0);
|
||||
expect(screen.getByText(/keine Rechte, die ein API-Schlüssel tragen kann/)).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Schlüssel anlegen" })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
@@ -18,6 +18,7 @@ import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { ConfirmDialog } from "@/components/confirm-dialog";
|
||||
import { ToggleChip } from "@/components/form/toggle-chip";
|
||||
import { useAuth } from "@/lib/auth/auth-context";
|
||||
import { ApiKeyAllowlist, parseAllowlist } from "./api-key-allowlist";
|
||||
|
||||
// Vorschlag wie bisher: der Wabenhain-Shop liest. Alles andere wird bewusst angekreuzt.
|
||||
@@ -56,10 +57,15 @@ function keyState(apiKey: ApiKey): { label: string; active: boolean } {
|
||||
*/
|
||||
export function ApiKeysTab() {
|
||||
const queryClient = useQueryClient();
|
||||
const { hasPermission } = useAuth();
|
||||
// Der Server lässt nur Rechte zu, die der Anlegende selbst hat - das UI
|
||||
// bietet deshalb auch nur diese an, statt erst beim Anlegen abzulehnen.
|
||||
const grantable = API_KEY_SCOPES.filter((scope) => hasPermission(scope));
|
||||
const [name, setName] = useState("Wabenhain-Shop");
|
||||
const [expiresInDays, setExpiresInDays] = useState("");
|
||||
const [allowedIps, setAllowedIps] = useState("");
|
||||
const [scopes, setScopes] = useState<ApiKeyScope[]>([...DEFAULT_SCOPES]);
|
||||
const [chosenScopes, setScopes] = useState<ApiKeyScope[]>([...DEFAULT_SCOPES]);
|
||||
const scopes = chosenScopes.filter((scope) => grantable.includes(scope));
|
||||
const [created, setCreated] = useState<{
|
||||
name: string;
|
||||
token: string;
|
||||
@@ -202,8 +208,13 @@ export function ApiKeysTab() {
|
||||
</div>
|
||||
<fieldset className="flex w-full flex-col gap-1.5">
|
||||
<legend className="mb-1.5 text-sm font-medium">Was darf der Schlüssel?</legend>
|
||||
{grantable.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Sie haben selbst keine Rechte, die ein API-Schlüssel tragen kann.
|
||||
</p>
|
||||
) : null}
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-3">
|
||||
{API_KEY_SCOPES.map((scope) => (
|
||||
{grantable.map((scope) => (
|
||||
<ToggleChip
|
||||
key={scope}
|
||||
label={API_KEY_SCOPE_LABELS[scope]}
|
||||
|
||||
Reference in new issue
Block a user