feat: Add password reset functionality with request and reset forms
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
This commit is contained in:
495
scripts/api-onboarding-test.mjs
Normal file
495
scripts/api-onboarding-test.mjs
Normal file
@@ -0,0 +1,495 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user