feat: Implement price list import feature with preview and apply options feat: Create price rules management page with CRUD operations feat: Develop quotes management page with itemized quotes and status tracking feat: Introduce organization registration page for new users feat: Build suppliers management page with detailed supplier information feat: Create users management page for inviting and managing roles chore: Add TypeScript configuration for improved type checking chore: Set up Vite configuration for development server and API proxy chore: Add Vite environment type definitions for better TypeScript support
496 lines
23 KiB
JavaScript
496 lines
23 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const baseUrl = process.argv[2] ?? process.env.API_BASE_URL ?? "http://127.0.0.1:8080";
|
|
const email = `admin+${Date.now()}@example.com`;
|
|
|
|
async function request(method, path, body, token, options = {}) {
|
|
const response = await fetch(`${baseUrl}${path}`, {
|
|
method,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
},
|
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
});
|
|
const text = await response.text();
|
|
const data = text ? parseResponseBody(text) : {};
|
|
const expectedStatus = options.expectedStatus;
|
|
|
|
if (expectedStatus !== undefined) {
|
|
if (response.status !== expectedStatus) {
|
|
throw new Error(`${method} ${path} expected ${expectedStatus}, got ${response.status}: ${JSON.stringify(data)}`);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`${method} ${path} failed: ${response.status} ${JSON.stringify(data)}`);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
function parseResponseBody(text) {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return { message: text };
|
|
}
|
|
}
|
|
|
|
function assert(condition, message) {
|
|
if (!condition) throw new Error(message);
|
|
}
|
|
|
|
async function main() {
|
|
console.log(`testing onboarding api via ${baseUrl}`);
|
|
|
|
const registration = await request("POST", "/api/v1/registration/organization", {
|
|
organization_name: "Muster GmbH",
|
|
email,
|
|
accept_terms: true,
|
|
});
|
|
assert(registration.id, "registration id missing");
|
|
console.log(`registered ${registration.id}`);
|
|
|
|
const registrations = await request("GET", "/api/v1/admin/organization-registrations");
|
|
assert(
|
|
registrations.some((item) => item.id === registration.id),
|
|
"registration not found in list",
|
|
);
|
|
|
|
const detail = await request(
|
|
"GET",
|
|
`/api/v1/admin/organization-registrations/${registration.id}`,
|
|
);
|
|
assert(detail.organization_name === "Muster GmbH", "organization name did not decrypt");
|
|
assert(detail.email === email, "registration email mismatch");
|
|
|
|
const approval = await request(
|
|
"POST",
|
|
`/api/v1/admin/organization-registrations/${registration.id}/approve`,
|
|
);
|
|
assert(approval.organization_id, "organization id missing");
|
|
assert(approval.schema_name?.startsWith("company_"), "schema name missing");
|
|
assert(approval.dev_initial_password, "dev initial password missing");
|
|
console.log(`approved ${approval.organization_id}`);
|
|
|
|
const login = await request("POST", "/api/v1/auth/login", {
|
|
email,
|
|
password: approval.dev_initial_password,
|
|
});
|
|
assert(login.must_change_password === true, "must_change_password should be true");
|
|
assert(login.access_token, "access token missing");
|
|
assert(login.organization_id, "selected organization missing");
|
|
assert(login.organizations.length >= 1, "login organizations missing");
|
|
const token = login.access_token;
|
|
|
|
const selected = await request(
|
|
"POST",
|
|
"/api/v1/auth/select-organization",
|
|
{ organization_id: login.organization_id },
|
|
token,
|
|
);
|
|
assert(selected.selected === true, "organization was not selected");
|
|
|
|
const users = await request("GET", "/api/v1/organizations/current/users", undefined, token);
|
|
assert(users.length >= 1, "users list is empty");
|
|
assert(users.some((user) => user.email === email), "owner user missing");
|
|
|
|
const setup = await request("PUT", "/api/v1/organizations/current/setup", {
|
|
display_name: "Muster GmbH",
|
|
legal_form: "GmbH",
|
|
street: "Musterstrasse 1",
|
|
postal_code: "12345",
|
|
city: "Musterstadt",
|
|
country: "Deutschland",
|
|
vat_id: "",
|
|
email: "info@example.com",
|
|
phone: "",
|
|
default_tax_rate: "19",
|
|
default_payment_days: "14",
|
|
}, token);
|
|
assert(setup.saved === true, "organization setup was not saved");
|
|
|
|
const loadedSetup = await request("GET", "/api/v1/organizations/current/setup", undefined, token);
|
|
assert(loadedSetup.setup?.display_name === "Muster GmbH", "organization setup was not loaded");
|
|
assert(loadedSetup.setup?.street === "Musterstrasse 1", "organization setup street mismatch");
|
|
|
|
const numberRanges = await request("GET", "/api/v1/number-ranges", undefined, token);
|
|
assert(numberRanges.some((range) => range.code === "customers" && range.pattern === "KU{counter}"), "customer number range missing");
|
|
assert(numberRanges.some((range) => range.code === "items" && range.pattern === "AR{counter}"), "item number range missing");
|
|
assert(numberRanges.some((range) => range.code === "activities" && range.pattern === "AK{counter}"), "activity number range missing");
|
|
assert(numberRanges.some((range) => range.code === "outgoing_invoices" && range.pattern === "AR{counter}"), "invoice number range missing");
|
|
|
|
const cashDiscountTerm = await request("POST", "/api/v1/cash-discount-terms", {
|
|
code: "2-10-30",
|
|
name: "2 % Skonto bei Zahlung innerhalb von 10 Tagen",
|
|
discount_percent: "2.00",
|
|
discount_days: 10,
|
|
net_days: 30,
|
|
valid_from: null,
|
|
valid_until: null,
|
|
is_default_customer_term: true,
|
|
is_default_supplier_term: true,
|
|
is_active: true,
|
|
}, token);
|
|
assert(cashDiscountTerm.id, "cash discount term id missing");
|
|
const cashDiscountTerms = await request("GET", "/api/v1/cash-discount-terms", undefined, token);
|
|
assert(cashDiscountTerms.some((term) => term.id === cashDiscountTerm.id), "cash discount term missing");
|
|
|
|
const createdCustomer = await request("POST", "/api/v1/customers", {
|
|
customer_number: "",
|
|
name: "Beispielkunde GmbH",
|
|
status: "active",
|
|
details: {
|
|
street: "Kundenstraße 4",
|
|
postal_code: "54321",
|
|
city: "Kundenstadt",
|
|
country: "Deutschland",
|
|
email: "kunde@example.com",
|
|
phone: "01234 56789",
|
|
},
|
|
standard_discount_percent: "5.50",
|
|
cash_discount_term_id: cashDiscountTerm.id,
|
|
}, token);
|
|
assert(createdCustomer.id, "customer id missing");
|
|
assert(/^KU\d{3}\.\d{3}\.\d{3}$/.test(createdCustomer.customer_number), "customer number was not generated");
|
|
|
|
const customers = await request("GET", "/api/v1/customers", undefined, token);
|
|
const listedCustomer = customers.find((customer) => customer.id === createdCustomer.id);
|
|
assert(listedCustomer?.name === "Beispielkunde GmbH", "customer name was not loaded");
|
|
assert(listedCustomer?.details.city === "Kundenstadt", "customer details were not loaded");
|
|
assert(listedCustomer?.standard_discount_percent.startsWith("5.5"), "customer discount missing");
|
|
assert(listedCustomer?.cash_discount_term_id === cashDiscountTerm.id, "customer cash discount missing");
|
|
|
|
const updatedCustomer = await request("PUT", `/api/v1/customers/${createdCustomer.id}`, {
|
|
...createdCustomer,
|
|
name: "Beispielkunde AG",
|
|
standard_discount_percent: "7.00",
|
|
}, token);
|
|
assert(updatedCustomer.name === "Beispielkunde AG", "customer update failed");
|
|
|
|
const supplier = await request("POST", "/api/v1/suppliers", {
|
|
supplier_number: "",
|
|
name: "Beispiellieferant GmbH",
|
|
status: "active",
|
|
details: { street: "Lieferweg 1", postal_code: "10115", city: "Berlin", country: "Deutschland", email: "lieferant@example.com", phone: "" },
|
|
standard_discount_percent: "2.00",
|
|
cash_discount_term_id: cashDiscountTerm.id,
|
|
payment_days: 30,
|
|
}, token);
|
|
assert(/^LI\d{3}\.\d{3}\.\d{3}$/.test(supplier.supplier_number), "supplier number was not generated");
|
|
const suppliers = await request("GET", "/api/v1/suppliers", undefined, token);
|
|
assert(suppliers.some((record) => record.id === supplier.id && record.details.city === "Berlin"), "supplier CRUD failed");
|
|
assert(suppliers.some((record) => record.id === supplier.id && record.cash_discount_term_id === cashDiscountTerm.id), "supplier cash discount missing");
|
|
|
|
const item = await request("POST", "/api/v1/items", {
|
|
item_number: "",
|
|
name: "Montagestunde",
|
|
unit: "Std",
|
|
tax_rate: "19",
|
|
default_purchase_price: "40.00",
|
|
default_sales_price: "85.00",
|
|
status: "active",
|
|
}, token);
|
|
assert(/^AR\d{3}\.\d{3}\.\d{3}$/.test(item.item_number), "item number was not generated");
|
|
const updatedItem = await request("PUT", `/api/v1/items/${item.id}`, { ...item, default_sales_price: "95.00" }, token);
|
|
assert(updatedItem.default_sales_price === "95.00", "item update failed");
|
|
const priceHistory = await request("GET", `/api/v1/items/${item.id}/prices`, undefined, token);
|
|
assert(priceHistory.length >= 2, "item price history missing");
|
|
assert(priceHistory.some((entry) => entry.sales_price?.startsWith("95")), "updated item price history missing");
|
|
|
|
const priceListContent = [
|
|
"item_number;name;unit;tax_rate;purchase_price;sales_price",
|
|
"IMP-100;Importartikel;Stk;19;10.00;25.00",
|
|
`${item.item_number};Montagestunde Import;Std;19;42.00;99.00`,
|
|
].join("\n");
|
|
const importPreview = await request("POST", "/api/v1/imports/price-list/preview", {
|
|
source_name: "api-test-price-list.csv",
|
|
delimiter: ";",
|
|
content: priceListContent,
|
|
}, token);
|
|
assert(importPreview.total_rows === 2, "price import preview row count mismatch");
|
|
assert(importPreview.valid_rows === 2, "price import preview valid rows mismatch");
|
|
assert(importPreview.rows.some((row) => row.item_number === "IMP-100" && row.action === "create"), "price import create action missing");
|
|
assert(importPreview.rows.some((row) => row.item_number === item.item_number && row.action === "update"), "price import update action missing");
|
|
|
|
const importApply = await request("POST", "/api/v1/imports/price-list/apply", {
|
|
source_name: "api-test-price-list.csv",
|
|
delimiter: ";",
|
|
content: priceListContent,
|
|
}, token);
|
|
assert(importApply.import_id, "price import id missing");
|
|
assert(importApply.applied_rows === 2, "price import applied rows mismatch");
|
|
assert(importApply.error_rows === 0, "price import errors mismatch");
|
|
|
|
const importedItems = await request("GET", "/api/v1/items", undefined, token);
|
|
assert(importedItems.some((record) => record.item_number === "IMP-100" && record.name === "Importartikel"), "imported item missing");
|
|
const reloadedItem = importedItems.find((record) => record.id === item.id);
|
|
assert(reloadedItem?.default_sales_price?.startsWith("99"), "imported item price update missing");
|
|
const importedPriceHistory = await request("GET", `/api/v1/items/${item.id}/prices`, undefined, token);
|
|
assert(importedPriceHistory.some((entry) => entry.source.startsWith("import:") && entry.sales_price?.startsWith("99")), "import price history missing");
|
|
|
|
const connector = await request("POST", "/api/v1/api-connectors", {
|
|
code: "demo_price_api",
|
|
name: "Demo Preis API",
|
|
connector_type: "demo",
|
|
config: {
|
|
base_url: "https://example.test/api",
|
|
token: "secret",
|
|
delimiter: ";",
|
|
price_list_csv: [
|
|
"item_number;name;unit;tax_rate;purchase_price;sales_price",
|
|
"IMP-100;Importartikel API;Stk;19;11.00;27.00",
|
|
].join("\n"),
|
|
},
|
|
is_active: true,
|
|
sync_interval_minutes: 60,
|
|
}, token);
|
|
assert(connector.id, "api connector id missing");
|
|
assert(connector.config?.token === "secret", "api connector config roundtrip failed");
|
|
const connectors = await request("GET", "/api/v1/api-connectors", undefined, token);
|
|
assert(connectors.some((record) => record.id === connector.id && record.config?.base_url === "https://example.test/api"), "api connector list missing");
|
|
const connectorSync = await request("POST", `/api/v1/api-connectors/${connector.id}/sync`, {}, token);
|
|
assert(connectorSync.synced === true, "api connector sync failed");
|
|
assert(connectorSync.applied_rows === 1, "api connector price sync did not apply rows");
|
|
const syncedItems = await request("GET", "/api/v1/items", undefined, token);
|
|
assert(syncedItems.some((record) => record.item_number === "IMP-100" && record.default_sales_price?.startsWith("27")), "api connector price update missing");
|
|
const deletedConnector = await request("DELETE", `/api/v1/api-connectors/${connector.id}`, undefined, token);
|
|
assert(deletedConnector.deleted === true, "api connector deactivate failed");
|
|
|
|
const priceRule = await request("POST", "/api/v1/price-rules", {
|
|
code: "standard_import_markup",
|
|
name: "Standardaufschlag Import",
|
|
source_type: "import",
|
|
source_id: null,
|
|
markup_percent: "25.00",
|
|
rounding_mode: "cent",
|
|
is_active: true,
|
|
}, token);
|
|
assert(priceRule.id, "price rule id missing");
|
|
const priceRules = await request("GET", "/api/v1/price-rules", undefined, token);
|
|
assert(priceRules.some((record) => record.id === priceRule.id && record.markup_percent.startsWith("25")), "price rule list missing");
|
|
const updatedPriceRule = await request("PUT", `/api/v1/price-rules/${priceRule.id}`, {
|
|
...priceRule,
|
|
markup_percent: "30.00",
|
|
rounding_mode: "five_cent",
|
|
}, token);
|
|
assert(updatedPriceRule.rounding_mode === "five_cent", "price rule update failed");
|
|
const deletedPriceRule = await request("DELETE", `/api/v1/price-rules/${priceRule.id}`, undefined, token);
|
|
assert(deletedPriceRule.deleted === true, "price rule deactivate failed");
|
|
|
|
const quote = await request("POST", "/api/v1/quotes", {
|
|
quote_number: "",
|
|
customer_id: createdCustomer.id,
|
|
status: "draft",
|
|
valid_until: null,
|
|
cash_discount_term_id: cashDiscountTerm.id,
|
|
customer_discount_percent: "7.00",
|
|
notes: "Erstes Testangebot.",
|
|
items: [{
|
|
item_id: item.id,
|
|
description: "Montagestunde mit Sonderpreis",
|
|
quantity: "2.00",
|
|
unit_price: "90.00",
|
|
original_unit_price: "95.00",
|
|
discount_percent: "0.00",
|
|
tax_rate: "19.00",
|
|
}],
|
|
}, token);
|
|
assert(/^AN\d{3}\.\d{3}\.\d{3}$/.test(quote.quote_number), "quote number was not generated");
|
|
assert(quote.items.length === 1, "quote item missing");
|
|
assert(quote.items[0].price_overridden === true, "quote price override missing");
|
|
|
|
const quotes = await request("GET", "/api/v1/quotes", undefined, token);
|
|
assert(quotes.some((record) => record.id === quote.id && record.notes.includes("Testangebot")), "quote was not loaded");
|
|
const updatedQuote = await request("PUT", `/api/v1/quotes/${quote.id}`, {
|
|
...quote,
|
|
status: "sent",
|
|
items: quote.items.map((line) => ({ ...line, quantity: "3.00" })),
|
|
}, token);
|
|
assert(updatedQuote.status === "sent", "quote update failed");
|
|
const convertedInvoice = await request("POST", `/api/v1/quotes/${quote.id}/convert-to-invoice`, undefined, token);
|
|
assert(/^AR\d{3}\.\d{3}\.\d{3}$/.test(convertedInvoice.invoice_number), "converted invoice number missing");
|
|
assert(convertedInvoice.source_quote_id === quote.id, "converted invoice quote link missing");
|
|
assert(convertedInvoice.items.length === 1, "converted invoice item missing");
|
|
const invoices = await request("GET", "/api/v1/outgoing-invoices", undefined, token);
|
|
assert(invoices.some((record) => record.id === convertedInvoice.id), "outgoing invoice was not loaded");
|
|
const finalized = await request("POST", `/api/v1/outgoing-invoices/${convertedInvoice.id}/finalize`, undefined, token);
|
|
assert(finalized.finalized === true, "outgoing invoice finalize failed");
|
|
const deletedQuote = await request("DELETE", `/api/v1/quotes/${quote.id}`, undefined, token);
|
|
assert(deletedQuote.deleted === true, "quote cancel failed");
|
|
|
|
const incomingInvoice = await request("POST", "/api/v1/incoming-invoices", {
|
|
invoice_number: "EXT-10001",
|
|
supplier_id: supplier.id,
|
|
status: "received",
|
|
cash_discount_term_id: cashDiscountTerm.id,
|
|
invoice_date: null,
|
|
due_at: null,
|
|
items: [{
|
|
item_id: item.id,
|
|
description: "Einkauf Montagestunde",
|
|
quantity: "1.00",
|
|
unit_price: "40.00",
|
|
tax_rate: "19.00",
|
|
}],
|
|
}, token);
|
|
assert(incomingInvoice.id, "incoming invoice id missing");
|
|
assert(incomingInvoice.cash_discount_term_id === cashDiscountTerm.id, "incoming invoice cash discount missing");
|
|
const incomingInvoices = await request("GET", "/api/v1/incoming-invoices", undefined, token);
|
|
assert(incomingInvoices.some((record) => record.id === incomingInvoice.id), "incoming invoice was not loaded");
|
|
const deletedIncomingInvoice = await request("DELETE", `/api/v1/incoming-invoices/${incomingInvoice.id}`, undefined, token);
|
|
assert(deletedIncomingInvoice.deleted === true, "incoming invoice cancel failed");
|
|
|
|
const deletedItem = await request("DELETE", `/api/v1/items/${item.id}`, undefined, token);
|
|
assert(deletedItem.deleted === true, "item deactivate failed");
|
|
|
|
const deletedSupplier = await request("DELETE", `/api/v1/suppliers/${supplier.id}`, undefined, token);
|
|
assert(deletedSupplier.deleted === true, "supplier deactivate failed");
|
|
|
|
const deletedCustomer = await request("DELETE", `/api/v1/customers/${createdCustomer.id}`, undefined, token);
|
|
assert(deletedCustomer.deleted === true, "customer deactivate failed");
|
|
|
|
const activity = await request("POST", "/api/v1/activities", {
|
|
activity_number: null,
|
|
activity_type: "task",
|
|
title: "Angebot prüfen",
|
|
body: "Preise mit Kunde abstimmen.",
|
|
status: "open",
|
|
priority: "high",
|
|
due_at: null,
|
|
}, token);
|
|
assert(/^AK\d{3}\.\d{3}\.\d{3}$/.test(activity.activity_number), "activity number was not generated");
|
|
const activities = await request("GET", "/api/v1/activities", undefined, token);
|
|
assert(activities.some((record) => record.id === activity.id && record.body.includes("Preise")), "activity CRUD failed");
|
|
const deletedActivity = await request("DELETE", `/api/v1/activities/${activity.id}`, undefined, token);
|
|
assert(deletedActivity.deleted === true, "activity cancel failed");
|
|
|
|
const communication = await request("POST", "/api/v1/communications", {
|
|
communication_type: "email",
|
|
direction: "outbound",
|
|
subject: "Rückfrage zum Angebot",
|
|
body: "Kunde bittet um aktualisierte Dokumente.",
|
|
status: "open",
|
|
occurred_at: null,
|
|
links: [{ entity_type: "customer", entity_id: createdCustomer.id }],
|
|
}, token);
|
|
assert(communication.id, "communication id missing");
|
|
assert(communication.subject === "Rückfrage zum Angebot", "communication subject mismatch");
|
|
assert(communication.links.some((link) => link.entity_id === createdCustomer.id), "communication link missing");
|
|
const communications = await request("GET", "/api/v1/communications", undefined, token);
|
|
assert(communications.some((record) => record.id === communication.id), "communication list missing");
|
|
|
|
const documentContent = Buffer.from("Dokumentinhalt für Phase 6", "utf8").toString("base64");
|
|
const documentRecord = await request("POST", "/api/v1/documents", {
|
|
title: "Testdokument",
|
|
description: "Dokument für Phase 6",
|
|
file_name: "phase-6.txt",
|
|
content_type: "text/plain",
|
|
content_base64: documentContent,
|
|
links: [{ entity_type: "communication", entity_id: communication.id }],
|
|
}, token);
|
|
assert(documentRecord.id, "document id missing");
|
|
assert(documentRecord.latest_version?.file_name === "phase-6.txt", "document metadata missing");
|
|
const documents = await request("GET", "/api/v1/documents", undefined, token);
|
|
assert(documents.some((record) => record.id === documentRecord.id), "document list missing");
|
|
const downloadedDocument = await request("GET", `/api/v1/documents/${documentRecord.id}/download`, undefined, token);
|
|
assert(downloadedDocument.content_base64 === documentContent, "document download content mismatch");
|
|
const auditLog = await request("GET", `/api/v1/documents/${documentRecord.id}/audit-log`, undefined, token);
|
|
assert(auditLog.some((entry) => entry.action === "upload"), "document upload audit missing");
|
|
assert(auditLog.some((entry) => entry.action === "download"), "document download audit missing");
|
|
const deletedDocument = await request("DELETE", `/api/v1/documents/${documentRecord.id}`, undefined, token);
|
|
assert(deletedDocument.deleted === true, "document archive failed");
|
|
|
|
const invitedEmail = `user+${Date.now()}@example.com`;
|
|
const invitation = await request("POST", "/api/v1/organizations/current/invitations", {
|
|
email: invitedEmail,
|
|
roles: ["viewer"],
|
|
}, token);
|
|
assert(invitation.id, "invitation id missing");
|
|
assert(invitation.dev_invitation_token, "dev invitation token missing");
|
|
|
|
const invitedUsers = await request("GET", "/api/v1/organizations/current/users", undefined, token);
|
|
const invitedUser = invitedUsers.find((user) => user.user_id === invitation.user_id);
|
|
assert(invitedUser, "invited user missing");
|
|
const acceptedInvitation = await request("POST", "/api/v1/auth/accept-invitation", {
|
|
token: invitation.dev_invitation_token,
|
|
new_password: "InvitePass123",
|
|
new_password_confirm: "InvitePass123",
|
|
});
|
|
assert(acceptedInvitation.accepted === true, "invitation accept failed");
|
|
const invitedLogin = await request("POST", "/api/v1/auth/login", {
|
|
email: invitedEmail,
|
|
password: "InvitePass123",
|
|
});
|
|
assert(invitedLogin.access_token, "invited user login failed");
|
|
assert(invitedLogin.organization_id, "invited user selected organization missing");
|
|
const invitedToken = invitedLogin.access_token;
|
|
|
|
await request(
|
|
"POST",
|
|
"/api/v1/auth/select-organization",
|
|
{ organization_id: invitedLogin.organization_id },
|
|
invitedToken,
|
|
);
|
|
|
|
const deniedCustomerWrite = await request("POST", "/api/v1/customers", {
|
|
customer_number: "",
|
|
name: "Nicht erlaubt GmbH",
|
|
status: "active",
|
|
details: {
|
|
street: "Sperrweg 1",
|
|
postal_code: "12345",
|
|
city: "Teststadt",
|
|
country: "Deutschland",
|
|
email: "nicht-erlaubt@example.test",
|
|
phone: "",
|
|
},
|
|
standard_discount_percent: "0",
|
|
cash_discount_term_id: null,
|
|
}, invitedToken, { expectedStatus: 403 });
|
|
assert(deniedCustomerWrite.message === "Berechtigung fehlt", "viewer customer write was not forbidden");
|
|
|
|
const deniedRoleWrite = await request(
|
|
"PATCH",
|
|
`/api/v1/organizations/current/users/${invitation.user_id}/roles`,
|
|
{ roles: ["admin"] },
|
|
invitedToken,
|
|
{ expectedStatus: 403 },
|
|
);
|
|
assert(deniedRoleWrite.message === "Berechtigung fehlt", "viewer role write was not forbidden");
|
|
|
|
await request(
|
|
"PATCH",
|
|
`/api/v1/organizations/current/users/${invitation.user_id}/roles`,
|
|
{ roles: ["sales", "viewer"] },
|
|
token,
|
|
);
|
|
const updatedUsers = await request("GET", "/api/v1/organizations/current/users", undefined, token);
|
|
const updatedUser = updatedUsers.find((user) => user.user_id === invitation.user_id);
|
|
assert(updatedUser.roles.includes("sales"), "role change was not saved");
|
|
|
|
const resetRequest = await request("POST", "/api/v1/auth/request-password-reset", { email });
|
|
assert(resetRequest.queued === true, "password reset request failed");
|
|
assert(resetRequest.dev_reset_token, "dev reset token missing");
|
|
const resetPassword = await request("POST", "/api/v1/auth/reset-password", {
|
|
token: resetRequest.dev_reset_token,
|
|
new_password: "ResetPass123",
|
|
new_password_confirm: "ResetPass123",
|
|
});
|
|
assert(resetPassword.changed === true, "password reset failed");
|
|
const resetLogin = await request("POST", "/api/v1/auth/login", {
|
|
email,
|
|
password: "ResetPass123",
|
|
});
|
|
assert(resetLogin.access_token, "login after password reset failed");
|
|
|
|
console.log("onboarding api test ok");
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|