diff --git a/examples/capture_shapes.rs b/examples/capture_shapes.rs new file mode 100644 index 0000000..91e0617 --- /dev/null +++ b/examples/capture_shapes.rs @@ -0,0 +1,167 @@ +//! Records the *shape* of real Invoice Ninja responses as test fixtures. +//! +//! Mock servers written from the upstream source have been wrong about the +//! real thing before (system-log timestamps, `/subscriptions/steps`), so the +//! offline suite benefits from fixtures that came from a real server. This +//! writes them without writing any data: every string becomes `""`, every +//! number `0`, every bool `false`, and an array keeps only its first element. +//! What survives is the key names and the JSON types, which is what decoding +//! depends on. `tests/real_shapes.rs` refuses to pass if a fixture holds +//! anything else. +//! +//! GET requests only, so it is safe against any instance, but it still reads +//! real records in memory. Review the diff before committing. +//! +//! ```sh +//! IN_BASE_URL=http://localhost:8080/api/v1 IN_API_TOKEN=... \ +//! cargo run --example capture_shapes +//! ``` +//! +//! A resource with no rows on the instance writes nothing and is reported as +//! skipped, since an empty list says nothing about a row's shape. Populate a +//! disposable instance and re-run to capture more. + +use std::fs; +use std::path::Path; + +use serde_json::{json, Value}; + +/// Resources captured as a one-row list. The name doubles as the fixture +/// filename. +const LISTS: &[&str] = &[ + "activities", + "bank_integrations", + "bank_transaction_rules", + "bank_transactions", + "client_gateway_tokens", + "clients", + "companies", + "company_gateways", + "credits", + "designs", + "documents", + "expense_categories", + "expenses", + "group_settings", + "invoices", + "locations", + "payment_terms", + "payments", + "products", + "projects", + "purchase_orders", + "quotes", + "recurring_expenses", + "recurring_invoices", + "recurring_quotes", + "subscriptions", + "system_logs", + "tags", + "task_schedulers", + "task_statuses", + "tasks", + "tax_rates", + "tokens", + "users", + "vendors", + "webhooks", +]; + +/// Endpoints with no `data` envelope, captured whole. +const BARE: &[(&str, &str)] = &[ + ("statics", "statics"), + ("health_check", "health_check"), + ("subscription_steps", "subscriptions/steps"), + ("activity_feed", "activities?rows=1&reactv2=1"), +]; + +/// Replaces every value with a placeholder of the same JSON type. +fn shape(value: &Value) -> Value { + match value { + Value::Null => Value::Null, + Value::Bool(_) => json!(false), + Value::Number(n) if n.is_f64() => json!(0.0), + Value::Number(_) => json!(0), + Value::String(_) => json!(""), + // Only the first element: a list's rows share a shape, and keeping + // all of them would just be size. + Value::Array(items) => Value::Array(items.first().map(shape).into_iter().collect()), + Value::Object(map) => { + Value::Object(map.iter().map(|(k, v)| (k.clone(), shape(v))).collect()) + } + } +} + +#[tokio::main] +async fn main() { + let base = std::env::var("IN_BASE_URL").expect("set IN_BASE_URL"); + let token = std::env::var("IN_API_TOKEN").expect("set IN_API_TOKEN"); + let base = base.trim_end_matches('/'); + let out = Path::new("tests/fixtures"); + fs::create_dir_all(out).expect("create tests/fixtures"); + + let http = reqwest::Client::new(); + let get = |path: String| { + let http = http.clone(); + let token = token.clone(); + let url = format!("{base}/{path}"); + async move { + let response = http + .get(url) + .header("X-Api-Token", token) + .header("Accept", "application/json") + .header("X-Requested-With", "XMLHttpRequest") + .send() + .await + .map_err(|e| e.to_string())?; + let status = response.status(); + let body = response.text().await.map_err(|e| e.to_string())?; + if !status.is_success() { + return Err(format!("HTTP {status}")); + } + serde_json::from_str::(&body).map_err(|e| e.to_string()) + } + }; + + for name in LISTS { + // Deleted and archived rows included: on a disposable instance they + // are often all there is. + // `system_logs` answers the status filter with a 500, so fall back to + // the plain list when the filtered one fails. + let filtered = format!("{name}?per_page=1&status=active,archived,deleted"); + let result = match get(filtered).await { + Ok(body) => Ok(body), + Err(_) => get(format!("{name}?per_page=1")).await, + }; + match result { + Ok(body) => match body + .get("data") + .and_then(Value::as_array) + .and_then(|d| d.first()) + { + Some(row) => { + write(out, name, &json!({ "data": [shape(row)] })); + println!(" captured {name}"); + } + None => println!(" skipped {name}: no rows on this instance"), + }, + Err(e) => println!(" FAILED {name}: {e}"), + } + } + + for (name, path) in BARE { + match get((*path).to_string()).await { + Ok(body) => { + write(out, name, &shape(&body)); + println!(" captured {name}"); + } + Err(e) => println!(" FAILED {name}: {e}"), + } + } +} + +fn write(dir: &Path, name: &str, value: &Value) { + let mut text = serde_json::to_string_pretty(value).expect("serialise fixture"); + text.push('\n'); + fs::write(dir.join(format!("{name}.json")), text).expect("write fixture"); +} diff --git a/examples/seed_sample_data.rs b/examples/seed_sample_data.rs new file mode 100644 index 0000000..e2dc698 --- /dev/null +++ b/examples/seed_sample_data.rs @@ -0,0 +1,232 @@ +//! Fills a **disposable** Invoice Ninja instance with one record of each +//! common kind, so `capture_shapes` has real rows to take shapes from. +//! +//! ```sh +//! IN_SMOKE_WRITE_HOST=localhost IN_BASE_URL=http://localhost:8080/api/v1 \ +//! IN_API_TOKEN=... cargo run --example seed_sample_data +//! ``` +//! +//! **Never run this against production.** It creates records, advances +//! number sequences, and may fire webhooks. It uses the smoke test's guard: +//! `IN_SMOKE_WRITE_HOST` must equal the host in `IN_BASE_URL`. Nothing is +//! deleted afterwards; the instance is meant to be thrown away. +//! +//! Each step is independent: one that fails (a validation rule this script +//! did not anticipate) is reported and the rest carry on, with anything that +//! needed it skipped. + +use std::future::Future; +use std::process::ExitCode; + +use invoiceninjars::{Error, InvoiceNinjaClient}; +use serde::de::DeserializeOwned; +use serde_json::{json, Value}; + +fn model(value: Value) -> T { + serde_json::from_value(value).expect("seed body matches the model") +} + +fn describe(error: &Error) -> String { + match error { + Error::Api { errors, .. } if !errors.is_empty() => format!("{error} {errors:?}"), + _ => error.to_string(), + } +} + +/// Runs one creation, reporting the outcome and returning the new id. +async fn step(name: &str, call: impl Future>) -> Option { + match call.await { + Ok(created) => { + println!(" created {name}"); + Some(created) + } + Err(e) => { + println!(" FAILED {name}: {}", describe(&e)); + None + } + } +} + +#[tokio::main] +async fn main() -> ExitCode { + let base_url = std::env::var("IN_BASE_URL").expect("set IN_BASE_URL"); + let token = std::env::var("IN_API_TOKEN").expect("set IN_API_TOKEN"); + let host = reqwest::Url::parse(&base_url) + .ok() + .and_then(|u| u.host_str().map(str::to_string)) + .unwrap_or_default(); + if std::env::var("IN_SMOKE_WRITE_HOST").as_deref() != Ok(host.as_str()) { + eprintln!("refusing to write: set IN_SMOKE_WRITE_HOST={host} to confirm this instance is disposable"); + return ExitCode::FAILURE; + } + let c = InvoiceNinjaClient::new(&base_url, token).expect("client"); + println!("seeding {host}"); + + let line = + json!([{ "product_key": "seed-product", "quantity": 1, "cost": 10.0, "notes": "seed" }]); + + let client_id = step( + "client", + c.create_client(&model(json!({ + "name": "seed-client", + "contacts": [{ "first_name": "Seed", "last_name": "Contact", "email": "seed@example.com" }], + }))), + ) + .await + .and_then(|x| x.id); + let vendor_id = step( + "vendor", + c.create_vendor(&model(json!({ "name": "seed-vendor" }))), + ) + .await + .and_then(|x| x.id); + + step( + "product", + c.create_product(&model( + json!({ "product_key": "seed-product", "price": 10.0 }), + )), + ) + .await; + step( + "tax_rate", + c.create_tax_rate(&model(json!({ "name": "seed-tax", "rate": 5.0 }))), + ) + .await; + step( + "payment_term", + c.create_payment_term(&model(json!({ "name": "seed-term", "num_days": 15 }))), + ) + .await; + step( + "expense_category", + c.create_expense_category(&model(json!({ "name": "seed-category" }))), + ) + .await; + step( + "tag", + c.create_tag(&model( + json!({ "name": "seed-tag", "entity_type": "client" }), + )), + ) + .await; + step( + "group_setting", + c.create_group_setting(&model(json!({ "name": "seed-group" }))), + ) + .await; + step( + "webhook", + c.create_webhook(&model( + json!({ "event_id": "1", "target_url": "https://example.com/seed-hook" }), + )), + ) + .await; + step( + "expense", + c.create_expense(&model( + json!({ "amount": 5.0, "public_notes": "seed", "vendor_id": vendor_id }), + )), + ) + .await; + step( + "recurring_expense", + c.create_recurring_expense(&model( + json!({ "amount": 5.0, "frequency_id": 5, "vendor_id": vendor_id }), + )), + ) + .await; + + if let Some(vendor_id) = &vendor_id { + step( + "purchase_order", + c.create_purchase_order(&model( + json!({ "vendor_id": vendor_id, "line_items": line }), + )), + ) + .await; + } + + let Some(client_id) = client_id else { + println!("no client, so the client-dependent records are skipped"); + return ExitCode::FAILURE; + }; + + step( + "location", + c.create_location(&model( + json!({ "name": "seed-location", "client_id": client_id, "address1": "1 Seed St" }), + )), + ) + .await; + step( + "project", + c.create_project(&model( + json!({ "name": "seed-project", "client_id": client_id }), + )), + ) + .await; + step( + "task", + c.create_task(&model( + json!({ "description": "seed task", "client_id": client_id }), + )), + ) + .await; + step( + "quote", + c.create_quote(&model( + json!({ "client_id": client_id, "line_items": line }), + )), + ) + .await; + step( + "credit", + c.create_credit(&model( + json!({ "client_id": client_id, "line_items": line }), + )), + ) + .await; + step( + "recurring_invoice", + c.create_recurring_invoice(&model( + json!({ "client_id": client_id, "frequency_id": 5, "line_items": line }), + )), + ) + .await; + // Expected to 500 on 5.13.50: the `recurring_quotes` table has no + // `design_id` column, so the server's own insert fails. Not an SDK bug. + step( + "recurring_quote", + c.create_recurring_quote(&model( + json!({ "client_id": client_id, "frequency_id": 5, "line_items": line }), + )), + ) + .await; + + let invoice = step( + "invoice", + c.create_invoice(&model( + json!({ "client_id": client_id, "line_items": line }), + )), + ) + .await; + if let Some(id) = invoice.and_then(|i| i.id) { + // A payment can only be applied to an invoice that has been sent. + if step("invoice mark_sent", c.mark_invoice_sent(&id)) + .await + .is_some() + { + step( + "payment", + c.create_payment(&model(json!({ + "client_id": client_id, + "amount": 5.0, + "invoices": [{ "invoice_id": id, "amount": 5.0 }], + }))), + ) + .await; + } + } + ExitCode::SUCCESS +} diff --git a/tests/fixtures/activities.json b/tests/fixtures/activities.json new file mode 100644 index 0000000..6f0c4d3 --- /dev/null +++ b/tests/fixtures/activities.json @@ -0,0 +1,29 @@ +{ + "data": [ + { + "activity_type_id": "", + "client_id": "", + "company_id": "", + "contact_id": "", + "created_at": 0, + "credit_id": "", + "expense_id": "", + "id": "", + "invoice_id": "", + "ip": "", + "is_system": false, + "notes": "", + "payment_id": "", + "purchase_order_id": "", + "quote_id": "", + "recurring_expense_id": "", + "recurring_invoice_id": "", + "task_id": "", + "token_id": "", + "updated_at": 0, + "user_id": "", + "vendor_contact_id": "", + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/activity_feed.json b/tests/fixtures/activity_feed.json new file mode 100644 index 0000000..2e343e6 --- /dev/null +++ b/tests/fixtures/activity_feed.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "activity_type_id": 0, + "created_at": 0, + "hashed_id": "", + "id": "", + "ip": "", + "notes": "", + "purchase_order": { + "hashed_id": "", + "label": "" + }, + "user": { + "hashed_id": "", + "label": "" + }, + "vendor": { + "hashed_id": "", + "label": "" + } + } + ] +} diff --git a/tests/fixtures/clients.json b/tests/fixtures/clients.json new file mode 100644 index 0000000..741dd0c --- /dev/null +++ b/tests/fixtures/clients.json @@ -0,0 +1,114 @@ +{ + "data": [ + { + "address1": "", + "address2": "", + "archived_at": 0, + "assigned_user_id": "", + "balance": 0, + "city": "", + "classification": "", + "client_hash": "", + "contacts": [ + { + "archived_at": 0, + "can_sign": false, + "cc_only": false, + "contact_key": "", + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "email": "", + "first_name": "", + "id": "", + "is_locked": false, + "is_primary": false, + "last_login": 0, + "last_name": "", + "link": "", + "password": "", + "phone": "", + "send_email": false, + "updated_at": 0 + } + ], + "country_id": "", + "created_at": 0, + "credit_balance": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "display_name": "", + "documents": [ + { + "archived_at": 0, + "assigned_user_id": "", + "created_at": 0, + "disk": "", + "documentable_id": "", + "documentable_type": "", + "hash": "", + "height": 0, + "id": "", + "is_default": false, + "is_deleted": false, + "is_public": false, + "link": "", + "name": "", + "preview": "", + "project_id": "", + "size": 0, + "type": "", + "updated_at": 0, + "url": "", + "user_id": "", + "vendor_id": "", + "width": 0 + } + ], + "e_invoice": {}, + "gateway_tokens": [], + "group_settings_id": "", + "has_valid_vat_number": false, + "id": "", + "id_number": "", + "industry_id": "", + "is_deleted": false, + "is_tax_exempt": false, + "last_login": 0, + "locations": [], + "name": "", + "number": "", + "paid_to_date": 0, + "payment_balance": 0, + "phone": "", + "postal_code": "", + "private_notes": "", + "public_notes": "", + "routing_id": "", + "settings": { + "currency_id": "", + "entity": "", + "industry_id": null, + "size_id": null + }, + "shipping_address1": "", + "shipping_address2": "", + "shipping_city": "", + "shipping_country_id": "", + "shipping_postal_code": "", + "shipping_state": "", + "size_id": "", + "state": "", + "tags": [], + "tax_info": {}, + "updated_at": 0, + "user_id": "", + "vat_number": "", + "website": "" + } + ] +} diff --git a/tests/fixtures/companies.json b/tests/fixtures/companies.json new file mode 100644 index 0000000..de12598 --- /dev/null +++ b/tests/fixtures/companies.json @@ -0,0 +1,1059 @@ +{ + "data": [ + { + "archived_at": 0, + "auto_start_tasks": false, + "calculate_expense_tax_by_amount": false, + "calculate_taxes": false, + "client_can_register": false, + "client_registration_fields": [ + { + "key": "", + "required": false, + "visible": false + } + ], + "company_key": "", + "convert_expense_currency": false, + "convert_payment_currency": false, + "convert_products": false, + "convert_rate_to_client": false, + "created_at": 0, + "custom_fields": {}, + "custom_surcharge_taxes1": false, + "custom_surcharge_taxes2": false, + "custom_surcharge_taxes3": false, + "custom_surcharge_taxes4": false, + "default_password_timeout": 0, + "default_quantity": false, + "default_task_is_date_based": false, + "documents": [], + "e_invoice": {}, + "enable_applying_payments": false, + "enable_modules": false, + "enable_product_cost": false, + "enable_product_discount": false, + "enable_product_quantity": false, + "enable_shop_api": false, + "enabled_expense_tax_rates": 0, + "enabled_item_tax_rates": 0, + "enabled_modules": 0, + "enabled_tax_rates": 0, + "expense_amount_is_pretax": false, + "expense_inclusive_taxes": false, + "expense_mailbox": "", + "expense_mailbox_active": false, + "fill_products": false, + "first_day_of_week": "", + "first_month_of_year": "", + "google_analytics_key": "", + "google_analytics_url": "", + "has_e_invoice_certificate": false, + "has_e_invoice_certificate_passphrase": false, + "hide_empty_columns_on_pdf": false, + "id": "", + "inbound_mailbox_allow_clients": false, + "inbound_mailbox_allow_company_users": false, + "inbound_mailbox_allow_unknown": false, + "inbound_mailbox_allow_vendors": false, + "inbound_mailbox_blacklist": "", + "inbound_mailbox_whitelist": "", + "industry_id": "", + "inventory_notification_threshold": 0, + "invoice_expense_documents": false, + "invoice_task_datelog": false, + "invoice_task_documents": false, + "invoice_task_hours": false, + "invoice_task_item_description": false, + "invoice_task_lock": false, + "invoice_task_project": false, + "invoice_task_project_header": false, + "invoice_task_timelog": false, + "is_disabled": false, + "is_large": false, + "legal_entity_id": null, + "mark_expenses_invoiceable": false, + "mark_expenses_paid": false, + "markdown_email_enabled": false, + "markdown_enabled": false, + "matomo_id": "", + "matomo_url": "", + "notify_vendor_when_paid": false, + "oauth_password_required": false, + "origin_tax_data": {}, + "portal_domain": "", + "portal_mode": "", + "quickbooks": null, + "report_include_deleted": false, + "report_include_drafts": false, + "session_timeout": 0, + "settings": { + "accept_client_input_quote_approval": false, + "address1": "", + "address2": "", + "all_pages_footer": false, + "all_pages_header": false, + "allow_billable_task_items": false, + "auto_archive_invoice": false, + "auto_archive_invoice_cancelled": false, + "auto_archive_quote": false, + "auto_bill": "", + "auto_bill_date": "", + "auto_bill_standard_invoices": false, + "auto_convert_quote": false, + "auto_email_invoice": false, + "bcc_email": "", + "besr_id": "", + "brevo_secret": "", + "city": "", + "classification": "", + "client_can_register": false, + "client_initiated_payments": false, + "client_initiated_payments_minimum": 0, + "client_manual_payment_notification": false, + "client_number_counter": 0, + "client_number_pattern": "", + "client_online_payment_notification": false, + "client_portal_allow_over_payment": false, + "client_portal_allow_under_payment": false, + "client_portal_enable_uploads": false, + "client_portal_privacy_policy": "", + "client_portal_terms": "", + "client_portal_under_payment_minimum": 0, + "company_gateway_ids": "", + "company_logo": "", + "company_logo_size": "", + "counter_number_applied": "", + "counter_padding": 0, + "country_id": "", + "credit_design_id": "", + "credit_footer": "", + "credit_number_counter": 0, + "credit_number_pattern": "", + "credit_terms": "", + "currency_id": "", + "custom_message_dashboard": "", + "custom_message_paid_invoice": "", + "custom_message_unapproved_quote": "", + "custom_message_unpaid_invoice": "", + "custom_sending_email": "", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date_format_id": "", + "default_expense_payment_type_id": "", + "default_task_rate": 0, + "delivery_note_design_id": "", + "document_email_attachment": false, + "documents_public_by_default": false, + "e_expense_forward_email": "", + "e_invoice_forward_email": "", + "e_invoice_type": "", + "e_quote_type": "", + "email": "", + "email_alignment": "", + "email_from_name": "", + "email_quote_subject_reminder1": "", + "email_quote_template_reminder1": "", + "email_sending_method": "", + "email_signature": "", + "email_style": "", + "email_style_custom": "", + "email_subject_credit": "", + "email_subject_custom1": "", + "email_subject_custom2": "", + "email_subject_custom3": "", + "email_subject_invoice": "", + "email_subject_payment": "", + "email_subject_payment_failed": "", + "email_subject_payment_partial": "", + "email_subject_purchase_order": "", + "email_subject_quote": "", + "email_subject_reminder1": "", + "email_subject_reminder2": "", + "email_subject_reminder3": "", + "email_subject_reminder_endless": "", + "email_subject_statement": "", + "email_template_credit": "", + "email_template_custom1": "", + "email_template_custom2": "", + "email_template_custom3": "", + "email_template_invoice": "", + "email_template_payment": "", + "email_template_payment_failed": "", + "email_template_payment_partial": "", + "email_template_purchase_order": "", + "email_template_quote": "", + "email_template_reminder1": "", + "email_template_reminder2": "", + "email_template_reminder3": "", + "email_template_reminder_endless": "", + "email_template_statement": "", + "embed_documents": false, + "enable_client_portal": false, + "enable_client_portal_dashboard": false, + "enable_client_portal_password": false, + "enable_client_portal_tasks": false, + "enable_client_profile_update": false, + "enable_e_invoice": false, + "enable_email_markup": false, + "enable_quote_reminder1": false, + "enable_rappen_rounding": false, + "enable_reminder1": false, + "enable_reminder2": false, + "enable_reminder3": false, + "enable_reminder_endless": false, + "endless_reminder_frequency_id": "", + "entity_send_time": 0, + "expense_number_counter": 0, + "expense_number_pattern": "", + "font_size": 0, + "france_reporting_enabled": false, + "france_reporting_schedule": "", + "global_tag_inheritance": false, + "gmail_sending_user_id": "", + "hide_empty_columns_on_pdf": false, + "hide_paid_to_date": false, + "id_number": "", + "inclusive_taxes": false, + "invoice_design_id": "", + "invoice_footer": "", + "invoice_labels": "", + "invoice_number_counter": 0, + "invoice_number_pattern": "", + "invoice_taxes": 0, + "invoice_terms": "", + "language_id": "", + "late_fee_amount1": 0, + "late_fee_amount2": 0, + "late_fee_amount3": 0, + "late_fee_endless_amount": 0, + "late_fee_endless_percent": 0, + "late_fee_percent1": 0, + "late_fee_percent2": 0, + "late_fee_percent3": 0, + "lock_invoices": "", + "mailgun_domain": "", + "mailgun_endpoint": "", + "mailgun_secret": "", + "merge_e_invoice_to_pdf": false, + "military_time": false, + "name": "", + "num_days_reminder1": 0, + "num_days_reminder2": 0, + "num_days_reminder3": 0, + "page_layout": "", + "page_numbering": false, + "page_numbering_alignment": "", + "page_size": "", + "payment_email_all_contacts": false, + "payment_flow": "", + "payment_number_counter": 0, + "payment_number_pattern": "", + "payment_receipt_design_id": "", + "payment_refund_design_id": "", + "payment_terms": "", + "payment_type_id": "", + "pdf_email_attachment": false, + "pdf_variables": { + "client_details": [ + "" + ], + "company_address": [ + "" + ], + "company_details": [ + "" + ], + "credit_details": [ + "" + ], + "delivery_note_columns": [ + "" + ], + "invoice_details": [ + "" + ], + "product_columns": [ + "" + ], + "product_quote_columns": [ + "" + ], + "purchase_order_details": [ + "" + ], + "quote_details": [ + "" + ], + "statement_credit_columns": [ + "" + ], + "statement_details": [ + "" + ], + "statement_invoice_columns": [ + "" + ], + "statement_payment_columns": [ + "" + ], + "statement_unapplied_columns": [ + "" + ], + "task_columns": [ + "" + ], + "total_columns": [ + "" + ], + "vendor_details": [ + "" + ] + }, + "phone": "", + "portal_custom_css": "", + "portal_custom_footer": "", + "portal_custom_head": "", + "portal_custom_js": "", + "portal_design_id": "", + "postal_code": "", + "postmark_secret": "", + "preference_product_notes_for_html_view": false, + "primary_color": "", + "primary_font": "", + "project_number_counter": 0, + "project_number_pattern": "", + "purchase_order_design_id": "", + "purchase_order_footer": "", + "purchase_order_number_counter": 0, + "purchase_order_number_pattern": "", + "purchase_order_public_notes": "", + "purchase_order_terms": "", + "qr_iban": "", + "quote_design_id": "", + "quote_footer": "", + "quote_late_fee_amount1": 0, + "quote_late_fee_percent1": 0, + "quote_num_days_reminder1": 0, + "quote_number_applied": "", + "quote_number_counter": 0, + "quote_number_pattern": "", + "quote_schedule_reminder1": "", + "quote_terms": "", + "recurring_expense_number_counter": 0, + "recurring_expense_number_pattern": "", + "recurring_invoice_number_counter": 0, + "recurring_invoice_number_pattern": "", + "recurring_number_prefix": "", + "recurring_quote_number_counter": 0, + "recurring_quote_number_pattern": "", + "reminder_send_time": 0, + "reply_to_email": "", + "reply_to_name": "", + "require_invoice_signature": false, + "require_purchase_order_signature": false, + "require_quote_signature": false, + "reset_counter_date": "", + "reset_counter_frequency_id": "", + "schedule_reminder1": "", + "schedule_reminder2": "", + "schedule_reminder3": "", + "secondary_color": "", + "secondary_font": "", + "send_email_on_mark_paid": false, + "send_reminders": false, + "ses_access_key": "", + "ses_from_address": "", + "ses_region": "", + "ses_secret_key": "", + "ses_topic_arn": "", + "shared_invoice_credit_counter": false, + "shared_invoice_quote_counter": false, + "show_accept_invoice_terms": false, + "show_accept_quote_terms": false, + "show_all_tasks_client_portal": "", + "show_currency_code": false, + "show_email_footer": false, + "show_paid_stamp": false, + "show_pdfhtml_on_mobile": false, + "show_shipping_address": false, + "show_task_item_description": false, + "signature_on_pdf": false, + "skip_automatic_email_with_peppol": false, + "state": "", + "statement_design_id": "", + "sync_invoice_quote_columns": false, + "task_number_counter": 0, + "task_number_pattern": "", + "task_round_to_nearest": 0, + "task_round_up": false, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "ticket_number_counter": 0, + "ticket_number_pattern": "", + "timezone_id": "", + "translations": {}, + "ubl_email_attachment": false, + "unlock_invoice_documents_after_payment": false, + "use_credits_payment": "", + "use_unapplied_payment": "", + "valid_until": "", + "vat_number": "", + "vendor_number_counter": 0, + "vendor_number_pattern": "", + "vendor_portal_enable_uploads": false, + "website": "" + }, + "show_product_cost": false, + "show_product_details": false, + "show_task_end_date": false, + "show_tasks_table": false, + "size_id": "", + "slack_webhook_url": "", + "smtp_encryption": "", + "smtp_host": "", + "smtp_local_domain": "", + "smtp_password": "", + "smtp_port": 0, + "smtp_username": "", + "smtp_verify_peer": false, + "stock_notification": false, + "stop_on_unpaid_recurring": false, + "subdomain": "", + "tax_data": { + "acts_as_receiver": false, + "acts_as_sender": false, + "model": null, + "regions": { + "AD": { + "has_sales_above_threshold": false, + "subregions": { + "AD": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + } + }, + "tax_all_subregions": false, + "tax_threshold": 0 + }, + "AU": { + "has_sales_above_threshold": false, + "subregions": { + "AU": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + } + }, + "tax_all_subregions": false, + "tax_threshold": 0 + }, + "EU": { + "has_sales_above_threshold": false, + "subregions": { + "AT": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "BE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "BG": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "CY": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "CZ": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "DE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "DK": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "EE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ES": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ES-CE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ES-CN": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ES-ML": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "FI": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "FR": { + "apply_tax": false, + "reduced_tax_rate": 0.0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "GR": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "HR": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "HU": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IS": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IT": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "LI": { + "apply_tax": false, + "reduced_tax_rate": 0.0, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "LT": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "LU": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "LV": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "MT": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "NL": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "NO": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "PL": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "PT": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "RO": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "SE": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "SI": { + "apply_tax": false, + "reduced_tax_rate": 0.0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "SK": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + } + }, + "tax_all_subregions": false, + "tax_threshold": 0 + }, + "SG": { + "has_sales_above_threshold": false, + "subregions": { + "SG": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + } + }, + "tax_all_subregions": false, + "tax_threshold": 0 + }, + "UK": { + "has_sales_above_threshold": false, + "subregions": { + "GB": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "GB-NIR": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IM": { + "apply_tax": false, + "reduced_tax_rate": 0, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + } + }, + "tax_all_subregions": false, + "tax_threshold": 0 + }, + "US": { + "has_sales_above_threshold": false, + "subregions": { + "AK": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "AL": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "AR": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "AZ": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "CA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "CO": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "CT": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "DE": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "FL": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "GA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "HI": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ID": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "IL": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "IN": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "KS": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "KY": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "LA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "MA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "MD": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "ME": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "MI": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "MN": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "MO": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "MS": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "MT": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "NC": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "ND": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "NE": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "NH": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "NJ": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "NM": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "NV": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "NY": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "OH": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "OK": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "OR": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "PA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "RI": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "SC": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "SD": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "TN": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "TX": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "UT": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "VA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "VT": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "WA": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0.0, + "vat_number": "" + }, + "WI": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "WV": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + }, + "WY": { + "apply_tax": false, + "tax_name": "", + "tax_rate": 0, + "vat_number": "" + } + }, + "tax_all_subregions": false + } + }, + "seller_subregion": "", + "version": "" + }, + "track_inventory": false, + "update_products": false, + "updated_at": 0, + "use_comma_as_decimal_place": false, + "use_credits_payment": "", + "use_quote_terms_on_conversion": false + } + ] +} diff --git a/tests/fixtures/credits.json b/tests/fixtures/credits.json new file mode 100644 index 0000000..d59a819 --- /dev/null +++ b/tests/fixtures/credits.json @@ -0,0 +1,139 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "backup": { + "adjustable_amount": 0, + "cancellation": { + "adjustment": 0, + "status_id": 0 + }, + "child_invoice_ids": [], + "document_type": null, + "e_invoice_cleared_at": null, + "e_invoice_status": null, + "guid": "", + "notes": null, + "parent_invoice_id": null, + "parent_invoice_number": null, + "redirect": null + }, + "balance": 0, + "client_id": "", + "created_at": 0, + "custom_surcharge1": 0, + "custom_surcharge2": 0, + "custom_surcharge3": 0, + "custom_surcharge4": 0, + "custom_surcharge_tax1": false, + "custom_surcharge_tax2": false, + "custom_surcharge_tax3": false, + "custom_surcharge_tax4": false, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "design_id": "", + "discount": 0, + "documents": [], + "due_date": "", + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "footer": "", + "has_expenses": false, + "has_tasks": false, + "id": "", + "invitations": [ + { + "archived_at": 0, + "can_sign": false, + "client_contact_id": "", + "created_at": 0, + "email_error": "", + "email_status": "", + "id": "", + "key": "", + "link": "", + "message_id": "", + "opened_date": "", + "sent_date": "", + "updated_at": 0, + "viewed_date": "" + } + ], + "invoice_id": "", + "is_amount_discount": false, + "is_deleted": false, + "last_sent_date": "", + "line_items": [ + { + "cost": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "discount": 0, + "expense_id": "", + "gross_line_total": 0, + "income_account_id": "", + "is_amount_discount": false, + "line_total": 0, + "net_cost": 0, + "notes": "", + "product_cost": 0, + "product_key": "", + "quantity": 0, + "sort_id": "", + "tags": "", + "task_id": "", + "tax_amount": 0, + "tax_id": "", + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "type_id": "", + "unit_code": "" + } + ], + "location_id": "", + "next_send_date": "", + "number": "", + "paid_to_date": 0, + "partial": 0, + "partial_due_date": "", + "po_number": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "reminder1_sent": "", + "reminder2_sent": "", + "reminder3_sent": "", + "reminder_last_sent": "", + "status_id": "", + "subscription_id": "", + "sync": null, + "tags": [], + "tax_info": {}, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "terms": "", + "total_taxes": 0, + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/designs.json b/tests/fixtures/designs.json new file mode 100644 index 0000000..2a94a48 --- /dev/null +++ b/tests/fixtures/designs.json @@ -0,0 +1,25 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "design": { + "body": "", + "footer": "", + "header": "", + "includes": "", + "product": "", + "task": "" + }, + "entities": "", + "id": "", + "is_active": false, + "is_custom": false, + "is_deleted": false, + "is_free": false, + "is_template": false, + "name": "", + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/documents.json b/tests/fixtures/documents.json new file mode 100644 index 0000000..acfe02b --- /dev/null +++ b/tests/fixtures/documents.json @@ -0,0 +1,29 @@ +{ + "data": [ + { + "archived_at": 0, + "assigned_user_id": "", + "created_at": 0, + "disk": "", + "documentable_id": "", + "documentable_type": "", + "hash": "", + "height": 0, + "id": "", + "is_default": false, + "is_deleted": false, + "is_public": false, + "link": "", + "name": "", + "preview": "", + "project_id": "", + "size": 0, + "type": "", + "updated_at": 0, + "url": "", + "user_id": "", + "vendor_id": "", + "width": 0 + } + ] +} diff --git a/tests/fixtures/expense_categories.json b/tests/fixtures/expense_categories.json new file mode 100644 index 0000000..ad7048c --- /dev/null +++ b/tests/fixtures/expense_categories.json @@ -0,0 +1,14 @@ +{ + "data": [ + { + "archived_at": 0, + "color": "", + "created_at": 0, + "id": "", + "is_deleted": false, + "name": "", + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/expenses.json b/tests/fixtures/expenses.json new file mode 100644 index 0000000..7c70083 --- /dev/null +++ b/tests/fixtures/expenses.json @@ -0,0 +1,55 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "bank_id": "", + "calculate_tax_by_amount": false, + "category_id": "", + "client_id": "", + "created_at": 0, + "currency_id": "", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "documents": [], + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "expense_currency_id": "", + "foreign_amount": 0, + "id": "", + "invoice_currency_id": "", + "invoice_documents": false, + "invoice_id": "", + "is_deleted": false, + "number": "", + "payment_date": "", + "payment_type_id": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "recurring_expense_id": "", + "should_be_invoiced": false, + "tags": [], + "tax_amount1": 0, + "tax_amount2": 0, + "tax_amount3": 0, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "transaction_id": "", + "transaction_reference": "", + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/group_settings.json b/tests/fixtures/group_settings.json new file mode 100644 index 0000000..6122763 --- /dev/null +++ b/tests/fixtures/group_settings.json @@ -0,0 +1,16 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "documents": [], + "id": "", + "is_deleted": false, + "name": "", + "settings": { + "entity": "" + }, + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/health_check.json b/tests/fixtures/health_check.json new file mode 100644 index 0000000..f073e50 --- /dev/null +++ b/tests/fixtures/health_check.json @@ -0,0 +1,37 @@ +{ + "api_version": "", + "cache_enabled": false, + "env_writable": false, + "exchange_rate_api_not_configured": false, + "exec": false, + "extensions": [ + { + "gd": false + } + ], + "file_permissions": "", + "flutter_renderer": "", + "is_docker": false, + "jobs_pending": 0, + "mail_mailer": "", + "open_basedir": false, + "pdf_engine": "", + "pending_migrations": false, + "phantom_enabled": false, + "php_version": { + "current_php_cli_version": "", + "current_php_version": "", + "is_okay": false, + "memory_limit": "", + "minimum_php_version": "" + }, + "queue": "", + "queue_data": { + "failed": 0, + "last_error": "", + "pending": 0 + }, + "simple_db_check": false, + "system_health": false, + "trailing_slash": false +} diff --git a/tests/fixtures/invoices.json b/tests/fixtures/invoices.json new file mode 100644 index 0000000..de369dd --- /dev/null +++ b/tests/fixtures/invoices.json @@ -0,0 +1,140 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "auto_bill_enabled": false, + "backup": { + "adjustable_amount": 0, + "cancellation": { + "adjustment": 0, + "status_id": 0 + }, + "child_invoice_ids": [], + "document_type": null, + "e_invoice_cleared_at": null, + "e_invoice_status": null, + "guid": "", + "notes": null, + "parent_invoice_id": null, + "parent_invoice_number": null, + "redirect": null + }, + "balance": 0, + "client_id": "", + "created_at": 0, + "custom_surcharge1": 0, + "custom_surcharge2": 0, + "custom_surcharge3": 0, + "custom_surcharge4": 0, + "custom_surcharge_tax1": false, + "custom_surcharge_tax2": false, + "custom_surcharge_tax3": false, + "custom_surcharge_tax4": false, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "design_id": "", + "discount": 0, + "documents": [], + "due_date": "", + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "footer": "", + "has_expenses": false, + "has_tasks": false, + "id": "", + "invitations": [ + { + "archived_at": 0, + "can_sign": false, + "client_contact_id": "", + "created_at": 0, + "email_error": "", + "email_status": "", + "id": "", + "key": "", + "link": "", + "message_id": "", + "opened_date": "", + "sent_date": "", + "updated_at": 0, + "viewed_date": "" + } + ], + "is_amount_discount": false, + "is_deleted": false, + "last_sent_date": "", + "line_items": [ + { + "cost": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "discount": 0, + "expense_id": "", + "gross_line_total": 0, + "income_account_id": "", + "is_amount_discount": false, + "line_total": 0, + "net_cost": 0, + "notes": "", + "product_cost": 0, + "product_key": "", + "quantity": 0, + "sort_id": "", + "tags": "", + "task_id": "", + "tax_amount": 0, + "tax_id": "", + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "type_id": "", + "unit_code": "" + } + ], + "location_id": "", + "next_send_date": "", + "number": "", + "paid_to_date": 0, + "partial": 0, + "partial_due_date": "", + "po_number": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "recurring_id": "", + "reminder1_sent": "", + "reminder2_sent": "", + "reminder3_sent": "", + "reminder_last_sent": "", + "status_id": "", + "subscription_id": "", + "sync": null, + "tags": [], + "tax_info": {}, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "terms": "", + "total_taxes": 0, + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/locations.json b/tests/fixtures/locations.json new file mode 100644 index 0000000..24a12e7 --- /dev/null +++ b/tests/fixtures/locations.json @@ -0,0 +1,27 @@ +{ + "data": [ + { + "address1": "", + "address2": "", + "archived_at": 0, + "city": "", + "client_id": "", + "country_id": "", + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "id": "", + "is_deleted": false, + "is_shipping_location": false, + "name": "", + "postal_code": "", + "state": "", + "tax_data": {}, + "updated_at": 0, + "user_id": "", + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/payment_terms.json b/tests/fixtures/payment_terms.json new file mode 100644 index 0000000..28f5300 --- /dev/null +++ b/tests/fixtures/payment_terms.json @@ -0,0 +1,13 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "id": "", + "is_deleted": false, + "name": "", + "num_days": 0, + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/payments.json b/tests/fixtures/payments.json new file mode 100644 index 0000000..ba60a00 --- /dev/null +++ b/tests/fixtures/payments.json @@ -0,0 +1,52 @@ +{ + "data": [ + { + "amount": 0, + "applied": 0, + "archived_at": 0, + "assigned_user_id": "", + "client_contact_id": "", + "client_id": "", + "company_gateway_id": "", + "created_at": 0, + "currency_id": "", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "documents": [], + "exchange_currency_id": "", + "exchange_rate": 0, + "gateway_type_id": "", + "id": "", + "invitation_id": "", + "is_deleted": false, + "is_manual": false, + "number": "", + "paymentables": [ + { + "amount": 0, + "archived_at": 0, + "created_at": 0, + "id": "", + "invoice_id": "", + "payment_id": "", + "refunded": 0, + "updated_at": 0 + } + ], + "private_notes": "", + "project_id": "", + "refunded": 0, + "status_id": "", + "tags": [], + "transaction_id": "", + "transaction_reference": "", + "type_id": "", + "updated_at": 0, + "user_id": "", + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/products.json b/tests/fixtures/products.json new file mode 100644 index 0000000..22731f0 --- /dev/null +++ b/tests/fixtures/products.json @@ -0,0 +1,37 @@ +{ + "data": [ + { + "archived_at": 0, + "assigned_user_id": "", + "cost": 0, + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "documents": [], + "id": "", + "in_stock_quantity": 0, + "income_account_id": "", + "is_deleted": false, + "max_quantity": 0, + "notes": "", + "price": 0, + "product_image": "", + "product_key": "", + "quantity": 0, + "stock_notification": false, + "stock_notification_threshold": 0, + "tags": [], + "tax_id": "", + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/projects.json b/tests/fixtures/projects.json new file mode 100644 index 0000000..62eec85 --- /dev/null +++ b/tests/fixtures/projects.json @@ -0,0 +1,30 @@ +{ + "data": [ + { + "archived_at": 0, + "assigned_user_id": "", + "budgeted_amount": 0, + "budgeted_hours": 0, + "client_id": "", + "color": "", + "created_at": 0, + "current_hours": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "documents": [], + "due_date": "", + "id": "", + "is_deleted": false, + "name": "", + "number": "", + "private_notes": "", + "public_notes": "", + "tags": [], + "task_rate": 0, + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/purchase_orders.json b/tests/fixtures/purchase_orders.json new file mode 100644 index 0000000..0185bd3 --- /dev/null +++ b/tests/fixtures/purchase_orders.json @@ -0,0 +1,93 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "balance": 0, + "client_id": "", + "created_at": 0, + "currency_id": "", + "custom_surcharge1": 0, + "custom_surcharge2": 0, + "custom_surcharge3": 0, + "custom_surcharge4": 0, + "custom_surcharge_tax1": false, + "custom_surcharge_tax2": false, + "custom_surcharge_tax3": false, + "custom_surcharge_tax4": false, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "design_id": "", + "discount": 0, + "documents": [], + "due_date": "", + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "expense_id": "", + "footer": "", + "has_expenses": false, + "has_tasks": false, + "id": "", + "invitations": [ + { + "archived_at": 0, + "can_sign": false, + "created_at": 0, + "email_error": "", + "email_status": "", + "id": "", + "key": "", + "link": "", + "message_id": "", + "opened_date": "", + "sent_date": "", + "updated_at": 0, + "vendor_contact_id": "", + "viewed_date": "" + } + ], + "invoice_id": "", + "is_amount_discount": false, + "is_deleted": false, + "last_sent_date": "", + "line_items": [], + "location_id": "", + "next_send_date": "", + "number": "", + "paid_to_date": 0, + "partial": 0, + "partial_due_date": "", + "po_number": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "quote_id": "", + "reminder1_sent": "", + "reminder2_sent": "", + "reminder3_sent": "", + "reminder_last_sent": "", + "status_id": "", + "subscription_id": "", + "sync": null, + "tags": [], + "tax_info": {}, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "terms": "", + "total_taxes": 0, + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/quotes.json b/tests/fixtures/quotes.json new file mode 100644 index 0000000..c54b226 --- /dev/null +++ b/tests/fixtures/quotes.json @@ -0,0 +1,123 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "balance": 0, + "client_id": "", + "created_at": 0, + "custom_surcharge1": 0, + "custom_surcharge2": 0, + "custom_surcharge3": 0, + "custom_surcharge4": 0, + "custom_surcharge_tax1": false, + "custom_surcharge_tax2": false, + "custom_surcharge_tax3": false, + "custom_surcharge_tax4": false, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "design_id": "", + "discount": 0, + "documents": [], + "due_date": "", + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "footer": "", + "has_expenses": false, + "has_tasks": false, + "id": "", + "invitations": [ + { + "archived_at": 0, + "can_sign": false, + "client_contact_id": "", + "created_at": 0, + "email_error": "", + "email_status": "", + "id": "", + "key": "", + "link": "", + "message_id": "", + "opened_date": "", + "sent_date": "", + "updated_at": 0, + "viewed_date": "" + } + ], + "invoice_id": "", + "is_amount_discount": false, + "is_deleted": false, + "last_sent_date": "", + "line_items": [ + { + "cost": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "discount": 0, + "expense_id": "", + "gross_line_total": 0, + "income_account_id": "", + "is_amount_discount": false, + "line_total": 0, + "net_cost": 0, + "notes": "", + "product_cost": 0, + "product_key": "", + "quantity": 0, + "sort_id": "", + "tags": "", + "task_id": "", + "tax_amount": 0, + "tax_id": "", + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "type_id": "", + "unit_code": "" + } + ], + "location_id": "", + "next_send_date": "", + "number": "", + "paid_to_date": 0, + "partial": 0, + "partial_due_date": "", + "po_number": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "reminder1_sent": "", + "reminder2_sent": "", + "reminder3_sent": "", + "reminder_last_sent": "", + "status_id": "", + "subscription_id": "", + "sync": null, + "tags": [], + "tax_info": {}, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "terms": "", + "total_taxes": 0, + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/recurring_expenses.json b/tests/fixtures/recurring_expenses.json new file mode 100644 index 0000000..83eb589 --- /dev/null +++ b/tests/fixtures/recurring_expenses.json @@ -0,0 +1,59 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "bank_id": "", + "calculate_tax_by_amount": false, + "category_id": "", + "client_id": "", + "created_at": 0, + "currency_id": "", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "documents": [], + "entity_type": "", + "exchange_rate": 0, + "foreign_amount": 0, + "frequency_id": "", + "id": "", + "invoice_currency_id": "", + "invoice_documents": false, + "invoice_id": "", + "is_deleted": false, + "last_sent_date": "", + "next_send_date": "", + "number": "", + "payment_date": "", + "payment_type_id": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "recurring_dates": [], + "recurring_expense_currency_id": "", + "remaining_cycles": 0, + "should_be_invoiced": false, + "status_id": "", + "tags": [], + "tax_amount1": 0, + "tax_amount2": 0, + "tax_amount3": 0, + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "transaction_id": "", + "transaction_reference": "", + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/recurring_invoices.json b/tests/fixtures/recurring_invoices.json new file mode 100644 index 0000000..1a9f6f2 --- /dev/null +++ b/tests/fixtures/recurring_invoices.json @@ -0,0 +1,90 @@ +{ + "data": [ + { + "amount": 0, + "archived_at": 0, + "assigned_user_id": "", + "auto_bill": "", + "auto_bill_enabled": false, + "balance": 0, + "client_id": "", + "created_at": 0, + "custom_surcharge1": 0, + "custom_surcharge2": 0, + "custom_surcharge3": 0, + "custom_surcharge4": 0, + "custom_surcharge_tax1": false, + "custom_surcharge_tax2": false, + "custom_surcharge_tax3": false, + "custom_surcharge_tax4": false, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "design_id": "", + "discount": 0, + "documents": [], + "due_date": "", + "due_date_days": "", + "e_invoice": {}, + "entity_type": "", + "exchange_rate": 0, + "footer": "", + "frequency_id": "", + "has_expenses": false, + "has_tasks": false, + "id": "", + "invitations": [ + { + "archived_at": 0, + "can_sign": false, + "client_contact_id": "", + "created_at": 0, + "email_error": "", + "email_status": "", + "id": "", + "key": "", + "link": "", + "message_id": "", + "opened_date": "", + "sent_date": "", + "updated_at": 0, + "viewed_date": "" + } + ], + "is_amount_discount": false, + "is_deleted": false, + "last_sent_date": "", + "line_items": [], + "location_id": "", + "next_send_date": "", + "next_send_datetime": "", + "number": "", + "paid_to_date": 0, + "partial": 0, + "partial_due_date": "", + "po_number": "", + "private_notes": "", + "project_id": "", + "public_notes": "", + "recurring_dates": [], + "remaining_cycles": 0, + "status_id": "", + "subscription_id": "", + "tags": [], + "tax_name1": "", + "tax_name2": "", + "tax_name3": "", + "tax_rate1": 0, + "tax_rate2": 0, + "tax_rate3": 0, + "terms": "", + "total_taxes": 0, + "updated_at": 0, + "user_id": "", + "uses_inclusive_taxes": false, + "vendor_id": "" + } + ] +} diff --git a/tests/fixtures/statics.json b/tests/fixtures/statics.json new file mode 100644 index 0000000..99f791b --- /dev/null +++ b/tests/fixtures/statics.json @@ -0,0 +1,185 @@ +{ + "bulk_updates": { + "client": [ + "" + ], + "expense": [ + "" + ], + "recurring_invoice": [ + "" + ], + "task": [ + "" + ] + }, + "countries": [ + { + "capital": "", + "citizenship": "", + "country_code": "", + "currency": "", + "currency_code": "", + "currency_sub_unit": "", + "decimal_separator": "", + "eea": false, + "full_name": "", + "id": "", + "iso_3166_2": "", + "iso_3166_3": "", + "name": "", + "region_code": "", + "sub_region_code": "", + "swap_currency_symbol": false, + "swap_postal_code": false, + "thousand_separator": "" + } + ], + "currencies": [ + { + "code": "", + "decimal_separator": "", + "exchange_rate": 0, + "id": "", + "name": "", + "precision": 0, + "swap_currency_symbol": false, + "symbol": "", + "thousand_separator": "" + } + ], + "date_formats": [ + { + "format": "", + "format_dart": "", + "format_moment": "", + "id": "" + } + ], + "datetime_formats": [ + { + "format": "", + "format_dart": "", + "format_moment": "", + "id": "" + } + ], + "gateways": [ + { + "created_at": 0, + "default_gateway_type_id": "", + "fields": "", + "id": "", + "is_offsite": false, + "is_secure": false, + "key": "", + "name": "", + "options": { + "1": { + "refund": false, + "token_billing": false, + "webhooks": [ + "" + ] + }, + "2": { + "refund": false, + "token_billing": false, + "webhooks": [ + "" + ] + } + }, + "provider": "", + "site_url": "", + "sort_order": 0, + "updated_at": 0, + "visible": false + } + ], + "industries": [ + { + "id": "", + "name": "" + } + ], + "languages": [ + { + "id": "", + "locale": "", + "name": "" + } + ], + "license_key": false, + "payment_types": [ + { + "gateway_type_id": 0, + "id": "", + "name": "" + } + ], + "sizes": [ + { + "id": "", + "name": "" + } + ], + "templates": { + "invoice": { + "body": "", + "subject": "" + }, + "partial_payment": { + "body": "", + "subject": "" + }, + "payment": { + "body": "", + "subject": "" + }, + "payment_failed": { + "body": "", + "subject": "" + }, + "purchase_order": { + "body": "", + "subject": "" + }, + "quote": { + "body": "", + "subject": "" + }, + "quote_reminder1": { + "body": "", + "subject": "" + }, + "reminder1": { + "body": "", + "subject": "" + }, + "reminder2": { + "body": "", + "subject": "" + }, + "reminder3": { + "body": "", + "subject": "" + }, + "reminder_endless": { + "body": "", + "subject": "" + }, + "statement": { + "body": "", + "subject": "" + } + }, + "timezones": [ + { + "id": "", + "location": "", + "name": "", + "utc_offset": 0 + } + ] +} diff --git a/tests/fixtures/subscription_steps.json b/tests/fixtures/subscription_steps.json new file mode 100644 index 0000000..c3350ec --- /dev/null +++ b/tests/fixtures/subscription_steps.json @@ -0,0 +1,18 @@ +{ + "App\\Livewire\\BillingPortal\\Authentication\\Login": { + "dependencies": [], + "id": "" + }, + "App\\Livewire\\BillingPortal\\Authentication\\Register": { + "dependencies": [], + "id": "" + }, + "App\\Livewire\\BillingPortal\\Authentication\\RegisterOrLogin": { + "dependencies": [], + "id": "" + }, + "App\\Livewire\\BillingPortal\\Cart\\Cart": { + "dependencies": [], + "id": "" + } +} diff --git a/tests/fixtures/system_logs.json b/tests/fixtures/system_logs.json new file mode 100644 index 0000000..3593786 --- /dev/null +++ b/tests/fixtures/system_logs.json @@ -0,0 +1,16 @@ +{ + "data": [ + { + "category_id": 0, + "client_id": "", + "company_id": "", + "created_at": 0, + "event_id": 0, + "id": "", + "log": "", + "type_id": 0, + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/tags.json b/tests/fixtures/tags.json new file mode 100644 index 0000000..c7d2d38 --- /dev/null +++ b/tests/fixtures/tags.json @@ -0,0 +1,14 @@ +{ + "data": [ + { + "archived_at": 0, + "color": null, + "created_at": 0, + "entity_type": "", + "id": "", + "is_deleted": false, + "name": "", + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/task_statuses.json b/tests/fixtures/task_statuses.json new file mode 100644 index 0000000..d6b0e91 --- /dev/null +++ b/tests/fixtures/task_statuses.json @@ -0,0 +1,14 @@ +{ + "data": [ + { + "archived_at": 0, + "color": "", + "created_at": 0, + "id": "", + "is_deleted": false, + "name": "", + "status_order": 0, + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/tasks.json b/tests/fixtures/tasks.json new file mode 100644 index 0000000..90cda5a --- /dev/null +++ b/tests/fixtures/tasks.json @@ -0,0 +1,36 @@ +{ + "data": [ + { + "archived_at": 0, + "assigned_user_id": "", + "client_id": "", + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "date": "", + "description": "", + "documents": [], + "due_date": "", + "duration": 0, + "estimated_duration": null, + "id": "", + "invoice_id": "", + "is_date_based": false, + "is_deleted": false, + "is_running": false, + "meta": {}, + "number": "", + "project_id": "", + "rate": 0, + "status_id": "", + "status_order": null, + "status_sort_order": 0, + "tags": [], + "time_log": "", + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/tax_rates.json b/tests/fixtures/tax_rates.json new file mode 100644 index 0000000..29dd3c2 --- /dev/null +++ b/tests/fixtures/tax_rates.json @@ -0,0 +1,13 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "id": "", + "is_deleted": false, + "name": "", + "rate": 0, + "updated_at": 0 + } + ] +} diff --git a/tests/fixtures/tokens.json b/tests/fixtures/tokens.json new file mode 100644 index 0000000..bd8fe44 --- /dev/null +++ b/tests/fixtures/tokens.json @@ -0,0 +1,15 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "id": "", + "is_deleted": false, + "is_system": false, + "name": "", + "token": "", + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/fixtures/users.json b/tests/fixtures/users.json new file mode 100644 index 0000000..f298049 --- /dev/null +++ b/tests/fixtures/users.json @@ -0,0 +1,44 @@ +{ + "data": [ + { + "archived_at": 0, + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "email": "", + "email_verified_at": 0, + "first_name": "", + "google_2fa_secret": false, + "has_password": false, + "id": "", + "is_deleted": false, + "language_id": "", + "last_confirmed_email_address": "", + "last_login": 0, + "last_name": "", + "oauth_provider_id": "", + "oauth_user_token": "", + "passkey_count": 0, + "passkey_enabled": false, + "phone": "", + "referral_code": "", + "referral_meta": { + "enterprise": 0, + "free": 0, + "pro": 0 + }, + "settings": { + "calendar_connection": { + "email": "", + "status": "" + } + }, + "signature": "", + "updated_at": 0, + "user_logged_in_notification": false, + "verified_phone_number": false + } + ] +} diff --git a/tests/fixtures/vendors.json b/tests/fixtures/vendors.json new file mode 100644 index 0000000..5489379 --- /dev/null +++ b/tests/fixtures/vendors.json @@ -0,0 +1,63 @@ +{ + "data": [ + { + "address1": "", + "address2": "", + "archived_at": 0, + "assigned_user_id": "", + "city": "", + "classification": "", + "contacts": [ + { + "archived_at": 0, + "can_sign": false, + "cc_only": false, + "created_at": 0, + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "email": "", + "first_name": "", + "id": "", + "is_primary": false, + "last_login": 0, + "last_name": "", + "link": "", + "password": "", + "phone": "", + "send_email": false, + "updated_at": 0 + } + ], + "country_id": "", + "created_at": 0, + "currency_id": "", + "custom_value1": "", + "custom_value2": "", + "custom_value3": "", + "custom_value4": "", + "display_name": "", + "documents": [], + "id": "", + "id_number": "", + "is_deleted": false, + "is_tax_exempt": false, + "language_id": "", + "last_login": 0, + "name": "", + "number": "", + "phone": "", + "postal_code": "", + "private_notes": "", + "public_notes": "", + "routing_id": "", + "state": "", + "tags": [], + "updated_at": 0, + "user_id": "", + "vat_number": "", + "website": "" + } + ] +} diff --git a/tests/fixtures/webhooks.json b/tests/fixtures/webhooks.json new file mode 100644 index 0000000..be42935 --- /dev/null +++ b/tests/fixtures/webhooks.json @@ -0,0 +1,18 @@ +{ + "data": [ + { + "archived_at": 0, + "company_id": "", + "created_at": 0, + "event_id": "", + "format": "", + "headers": [], + "id": "", + "is_deleted": false, + "rest_method": "", + "target_url": "", + "updated_at": 0, + "user_id": "" + } + ] +} diff --git a/tests/real_shapes.rs b/tests/real_shapes.rs new file mode 100644 index 0000000..b024382 --- /dev/null +++ b/tests/real_shapes.rs @@ -0,0 +1,227 @@ +//! Decodes fixtures captured from a real server. +//! +//! `tests/fixtures/*.json` are written by `examples/capture_shapes.rs` and +//! hold only key names and JSON types (strings are `""`, numbers `0`, bools +//! `false`). Mock responses written from the upstream source have been wrong +//! about the real thing before, so these pin the shapes a server actually +//! sent. To refresh them, seed a disposable instance with +//! `examples/seed_sample_data.rs` and re-run the capture. +//! +//! Three properties are checked: every fixture decodes into its model +//! (goal 2), a decode followed by a re-serialise loses no key (goal 1), and no +//! fixture contains a real value, so a careless capture cannot leak data. + +use std::collections::BTreeMap; +use std::fs; +use std::path::{Path, PathBuf}; + +use invoiceninjars::models::*; +use serde::{de::DeserializeOwned, Serialize}; +use serde_json::Value; + +fn dir() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures") +} + +fn load(name: &str) -> Value { + let path = dir().join(format!("{name}.json")); + let text = fs::read_to_string(&path).unwrap_or_else(|e| panic!("{}: {e}", path.display())); + serde_json::from_str(&text).unwrap_or_else(|e| panic!("{}: {e}", path.display())) +} + +/// The captured row of a list fixture. +fn row(name: &str) -> Value { + load(name)["data"][0].clone() +} + +/// Key paths present in `raw` but absent from `out`. Arrays are compared +/// through their first element, which is all a fixture keeps. +fn lost_keys(raw: &Value, out: &Value, prefix: &str, into: &mut Vec) { + match (raw, out) { + (Value::Object(raw), Value::Object(out)) => { + for (key, raw_value) in raw { + match out.get(key) { + Some(out_value) => { + lost_keys(raw_value, out_value, &format!("{prefix}.{key}"), into) + } + None => into.push(format!("{prefix}.{key}")), + } + } + } + (Value::Array(raw), Value::Array(out)) => { + if let (Some(raw), Some(out)) = (raw.first(), out.first()) { + lost_keys(raw, out, &format!("{prefix}[]"), into); + } + } + _ => {} + } +} + +/// Decodes `raw` as `T`, re-serialises it, and returns problems. +fn check(name: &str, raw: &Value) -> Vec { + let typed: T = match serde_json::from_value(raw.clone()) { + Ok(typed) => typed, + Err(e) => return vec![format!("{name}: does not decode: {e}")], + }; + let out = serde_json::to_value(&typed).expect("serialises"); + let mut lost = Vec::new(); + lost_keys(raw, &out, name, &mut lost); + lost.into_iter() + .filter(|path| !OMITTED_WHEN_EMPTY.contains(&path.as_str())) + .map(|path| format!("{path}: key lost on a decode/re-serialise")) + .collect() +} + +/// Fields that are deliberately left out of a request when empty, because the +/// server rejects the empty value (`""` becomes `NULL` on a non-null column, +/// or fails an `integer` rule). A fixture holds `""` placeholders, so these +/// show up as absent after a re-serialise. Adding to this list is a decision. +const OMITTED_WHEN_EMPTY: &[&str] = &[ + "locations.country_id", + "purchase_orders.status_id", + "webhooks.format", +]; + +/// One fixture per `(file, type)`; the macro also records which files it +/// covered so a new capture cannot sit untested. +macro_rules! list_fixtures { + ($($file:literal => $ty:ty),+ $(,)?) => { + const COVERED_LISTS: &[&str] = &[$($file),+]; + + fn check_lists() -> Vec { + let mut problems = Vec::new(); + $( problems.extend(check::<$ty>($file, &row($file))); )+ + problems + } + }; +} + +list_fixtures! { + "activities" => Activity, + "clients" => Client, + "companies" => Company, + "credits" => Credit, + "designs" => Design, + "documents" => Document, + "expense_categories" => ExpenseCategory, + "expenses" => Expense, + "group_settings" => GroupSetting, + "invoices" => Invoice, + "locations" => Location, + "payment_terms" => PaymentTerm, + "payments" => Payment, + "products" => Product, + "projects" => Project, + "purchase_orders" => PurchaseOrder, + "quotes" => Quote, + "recurring_expenses" => RecurringExpense, + "recurring_invoices" => RecurringInvoice, + "system_logs" => SystemLog, + "tags" => Tag, + "task_statuses" => TaskStatus, + "tasks" => Task, + "tax_rates" => TaxRate, + "tokens" => CompanyToken, + "users" => User, + "vendors" => Vendor, + "webhooks" => Webhook, +} + +/// Fixtures that are not a one-row list. +const COVERED_OTHER: &[&str] = &[ + "statics", + "health_check", + "subscription_steps", + "activity_feed", +]; + +#[test] +fn every_real_row_decodes_and_loses_no_key() { + let problems = check_lists(); + assert!( + problems.is_empty(), + "{} problem(s):\n{}", + problems.len(), + problems.join("\n") + ); +} + +#[test] +fn bare_endpoint_fixtures_decode() { + let mut problems = Vec::new(); + problems.extend(check::("statics", &load("statics"))); + problems.extend(check::("health_check", &load("health_check"))); + problems.extend(check::( + "activity_feed", + &load("activity_feed")["data"][0], + )); + + // Keyed by class name on a real server; see `subscription_steps`. + let steps: Result, _> = + serde_json::from_value(load("subscription_steps")); + match steps { + Ok(steps) => assert!(!steps.is_empty(), "the captured steps object is empty"), + Err(e) => problems.push(format!("subscription_steps: {e}")), + } + + assert!(problems.is_empty(), "{}", problems.join("\n")); +} + +#[test] +fn every_fixture_file_is_covered_by_a_test() { + let mut on_disk: Vec = fs::read_dir(dir()) + .expect("tests/fixtures exists") + .filter_map(|entry| { + let path = entry.ok()?.path(); + (path.extension()? == "json").then(|| path.file_stem()?.to_str().map(String::from))? + }) + .collect(); + on_disk.sort(); + + let mut covered: Vec = COVERED_LISTS + .iter() + .chain(COVERED_OTHER) + .map(|s| s.to_string()) + .collect(); + covered.sort(); + + assert_eq!( + on_disk, covered, + "a captured fixture has no test (or a test has no fixture)" + ); +} + +/// The capture tool replaces every value; this refuses to pass if any +/// survived, so real data cannot be committed by accident. +#[test] +fn fixtures_hold_shapes_and_never_values() { + fn walk(value: &Value, at: &str, bad: &mut Vec) { + match value { + Value::String(s) if !s.is_empty() => bad.push(format!("{at}: string value")), + Value::Number(n) if n.as_f64() != Some(0.0) => bad.push(format!("{at}: number value")), + Value::Bool(true) => bad.push(format!("{at}: true")), + Value::Array(items) => { + assert!(items.len() <= 1, "{at}: a fixture keeps one array element"); + items.iter().for_each(|v| walk(v, &format!("{at}[]"), bad)); + } + Value::Object(map) => map + .iter() + .for_each(|(k, v)| walk(v, &format!("{at}.{k}"), bad)), + _ => {} + } + } + + let mut bad = Vec::new(); + for entry in fs::read_dir(dir()).expect("tests/fixtures exists") { + let path = entry.unwrap().path(); + if path.extension().is_some_and(|e| e == "json") { + let name = path.file_stem().unwrap().to_string_lossy().to_string(); + walk(&load(&name), &name, &mut bad); + } + } + assert!( + bad.is_empty(), + "fixtures contain values:\n{}", + bad.join("\n") + ); +}