How we test

Every test we run.

Before each release, every one of these checks runs automatically against the real code — the Android and web app, the server, and the server against a real copy of the database. Search them, or pick an area.

518automated tests
518passed in the last run
279in the app
239on the server

Last run 3 October 2026 · all passing.

Billing, prices & dues Bills add up, numbers count up, each customer’s rate and tier applies, part payments and dues are right, and edits keep the books straight. 74/74
  • 20 people selling the last 15 units at once: 15 bills, stock 0, counter 15Server + database
  • A bill is saved whole: lines, receipts, stock, counter and bill number togetherServer + database
  • A bill worth nothing, or for no units, is not countedApp
  • A cash sale uses the standard price, not a customer rateApp
  • A customer with dues shows the balance before sellingApp
  • A customer with no agreed price falls back to last soldApp
  • A customer's lump payment clears the oldest bill first, then the nextServer + database
  • A full page hides its cut-off bill and loads moreApp
  • A non-numeric rate is rejected rather than stored as NaNServer
  • A price cannot end before it startsServer
  • A rate is stored as whole paiseServer
  • A rate that would round to zero or less is rejectedServer
  • A redelivered event is counted onceServer + database
  • A refusal is shown, never queuedApp
  • A retried bill is saved once; a reused id with other items is refusedServer + database
  • A walk-in bill without a name is a cash saleServer
  • A walk-in payment covers the bill oldest line first, once, never more than is dueServer + database
  • Agreed prices group by customer at width 1100.0App
  • Agreed prices group by customer at width 360.0App
  • An admin edits a bill: stock moves by the difference and the edit is auditedServer + database
  • An empty date means no limit rather than an errorServer
  • An empty price list is rejectedServer
  • An expired agreement is labelled, not hiddenApp
  • Bad input is refused before anything is readServer + database
  • Bill counts show as soon as they resolve, even while the stats bootstrap is still loadingApp
  • Bills, payments, edits and deletes keep every total equal to the historyServer + database
  • Cannot advance until a customer or cash sale is chosenApp
  • Changing the customer re-prices the item listApp
  • Checkout price selection a zero last-sold rate is ignored rather than offeredApp
  • Checkout price selection an agreed tier beats both history and the standard priceApp
  • Checkout price selection an expired agreement does not keep discountingApp
  • Checkout price selection falls back to the standard price for a new customerApp
  • Checkout price selection with no agreed price, what the customer last paid is offeredApp
  • Connected but the server is unreachable: the bill is kept on the device under the same idApp
  • Counts full invoices, not their product linesApp
  • Dates must be real calendar daysServer
  • Different quantity breaks may share a windowServer
  • Effective dates a newer tier supersedes an older open-ended oneApp
  • Effective dates a tier applies only inside its windowApp
  • Effective dates expired and pending tiers are reported for the owner viewApp
  • Effective dates window edges are calendar days, not instantsApp
  • Empty state explains what a customer price doesApp
  • Items are parsed into paise and checkedServer
  • Lines from older app versions build their bill and leave it when deletedServer + database
  • Margin checks a healthy margin produces no warningApp
  • Margin checks an unknown cost is not reported as a healthy marginApp
  • Margin checks flags a rate at or below costApp
  • Margin checks flags a thin margin without blocking itApp
  • Money handling a date-only string survives a round tripApp
  • Money handling a stored rupee rate is read back as the same paise valueApp
  • Money handling rate_paise wins over a stale rupee mirror of the same fieldApp
  • Money handling rates round to whole paise instead of driftingApp
  • More than twenty prices for one product are rejectedServer
  • One line short of stock saves nothing at allServer + database
  • Online, the bill is saved on the serverApp
  • Only the owner deletes a bill; stock and the counter come back, onceServer + database
  • Open-ended windows overlap everythingServer
  • Payment fills lines in order and every line adds up exactlyServer
  • Quantity breaks a quantity below the smallest break has no agreed priceApp
  • Quantity breaks must be whole numbers of at least oneServer
  • Quantity breaks picks the most specific tier the quantity reachesApp
  • Quantity breaks resolution does not depend on the order tiers are stored inApp
  • Quantity breaks suggests the next break the customer has not reachedApp
  • Search narrows to a matching productApp
  • Staff cannot add or edit pricesApp
  • Stock leaves and returns with its selling valueServer
  • The agreed price is already applied on the product listApp
  • The check finds totals that drifted, and --apply repairs themServer + database
  • The owner gets the controls staff do notApp
  • The plan blocks billing outside its dates, at its limit, or when not set upServer
  • The plan is checked with the server's clockServer + database
  • The sale opens on the customer step, not the product listApp
  • The same break is allowed once the windows stop touchingServer
  • Two tiers with the same break and overlapping dates are rejectedServer
Collections & payments Payments clear the oldest bills first, promises and follow-ups are tracked, and every rupee lands in the transaction history. 34/34
  • A payment names the bills it paid, oldest first, per billApp
  • Choosing a period reloads just that periodApp
  • Collections prioritize invoice age rather than smaller duesServer
  • Collections render and filter at width 1100.0App
  • Collections render and filter at width 360.0App
  • Credit days produce deadlines with correct day boundariesApp
  • Customer details show terms, promises and bill collectionsApp
  • Different bills, kinds or moments stay separateApp
  • Empty state is usable with no unpaid accountsApp
  • Equal-time invoice ordering is deterministic and paid lines are skippedServer
  • Explicit calendar deadlines take precedence over customer credit daysApp
  • Follow-up dates validate calendar dates and supported yearsServer
  • Follow-ups are idempotent and validate promises against current debtServer
  • Groups sale lines into bills and sums in integer paiseApp
  • IST calendar dates handle late UTC sales and month rolloverApp
  • One bill paid at billing shows as one entry for its full amountApp
  • One collection distributes across sale lines with exact paise arithmeticServer
  • Only collections count as customer-ledger paymentsServer
  • Outsiders and unauthenticated callers cannot collect another company's balanceServer
  • Overpayment fails without modifying any sale or creating a receiptServer
  • Overpayments, zero payments and invalid amounts cannot be allocatedServer
  • Payment commits all allocations, one receipt, and promise progress togetherServer
  • Promises are missed after their date, never on the promised dayApp
  • Receipts without a source are customer collectionsApp
  • Retry after a lost response or same-company user switch cannot charge twiceServer
  • Reusing a request ID with different payment details is rejectedServer
  • Reversals keep their signApp
  • Selected bill collections leave other bills untouchedServer
  • Settled customers remain available for their activity historyApp
  • Staff cannot change owner-managed payment termsServer
  • Staff cannot edit another member’s assigned follow-upApp
  • The Payments sheet tab leaves out money taken while billingServer
  • The summary email counts collections once, not counter payments againServer
  • Unknown payment terms never imply overdue debtApp
Sign-in, team & roles Google and mobile sign-in, invites by mobile number, joining a business, roles, and turning members off or removing them. 26/26
  • A business is created only with Google and a mobile numberApp
  • A day adds up what was sold and collectedApp
  • A failed profile read does not sign an active user outApp
  • A genuine sign-up failure is reported, not swallowedApp
  • A new business needs a Google email and a mobile number on one accountServer + database
  • An invite is joined by Google email too, and fails when expired or the plan lapsedServer + database
  • An invite link or a bare code both give the tokenApp
  • An invite matches its mobile number or its emailServer
  • An invite needs a mobile number; the email is optionalServer + database
  • Emails are lower-cased and checkedServer
  • Hints show enough to recognise and no moreServer
  • Indian mobile numbers are stored the way Firebase Auth reports themServer
  • Linking a number copies it to the profile, never someone else'sServer + database
  • Mobile numbers Indian mobiles become +91 and ten digitsApp
  • One free trial per mobile number, across businessesServer + database
  • Only a verified email and an OTP-proven number count as identityServer
  • People and invites are told apart by email, else mobileApp
  • Sale lines of one bill are one bill, newest firstApp
  • Sign-in offers Google and mobile; password only on requestApp
  • Signing in is not undone by a concurrent session refreshApp
  • Signing out clears the profileApp
  • Signing up leaves the new owner signed inApp
  • The invite email links to the join page and escapes namesServer
  • The invite link is joined with the invited number or email, onceServer + database
  • The owner turns a member off and on, or deletes them so they can start againServer + database
  • Trial claims are one per number and one per mailboxServer
Security & data walls One business can never read or change another’s data, staff see only what their role allows, and abuse is rate-limited. 99/99
  • A broken limiter fails open rather than taking the product downServer
  • A delivery's history is visible to its own company onlyServer + database
  • A fresh window resets the countServer
  • A member the owner turned off is refused, and cannot turn themselves onServer + database
  • A name that is not one of our UUIDs is refusedServer
  • A new company cannot grant itself paid accessServer + database
  • A new owner can create their company and profile at sign upServer + database
  • A sale is stamped with server time; only owners and admins keep a bill's past dateServer + database
  • A sale needs a plan whose dates include nowServer + database
  • A sale's money must add upServer + database
  • A URL belonging to another company is refusedServer
  • A URL on any other host is refusedServer
  • A URL our upload function issued is acceptedServer
  • Account and IP are separate bucketsServer
  • Admin and staff move stock figures but cannot edit the productServer + database
  • After the switch-over, money and stock are written only by the serverServer + database
  • An admin removes a sale line only as an audited bill edit; staff never edit billsServer + database
  • An embedded credential is refusedServer
  • An extension we do not store is refusedServer
  • An unauthenticated caller with no IP is not counted at allServer
  • AppPermissions admin runs the business but cannot manage team, products or deleteApp
  • AppPermissions owner can do everythingApp
  • AppPermissions staff sells and records expenses, and nothing moreApp
  • Bucket ids cannot escape their collectionServer
  • Calls made during a cooldown do not extend itServer
  • Calls under the limit are allowed and countedServer
  • CanOpenRoute admin opens everything except creating productsApp
  • CanOpenRoute owner opens every pageApp
  • CanOpenRoute staff are kept out of finance, operations and settingsApp
  • Clients cannot bypass validated collection callablesServer + database
  • Coupon codes cannot be listed, read, created or redeemed from the appServer + database
  • Customers with server totals can still be edited, but their totals cannotServer + database
  • Daily totals are readable by their own company and written by no clientServer + database
  • Edges a caller-supplied fallback is honouredApp
  • Edges null and empty fall backApp
  • Everyone records expensesServer + database
  • Known conditions map to guidance an expired session is explainedApp
  • Known conditions map to guidance bad credentials do not reveal which half was wrongApp
  • Known conditions map to guidance throttling is explainedApp
  • Limits can be switched off entirely for local runsServer
  • Messages the app writes are shown as-is a Failure message reaches the user unchangedApp
  • Messages the app writes are shown as-is a plain sentence from a Cloud Function is keptApp
  • Messages the app writes are shown as-is the rate limiter keeps its specific wait timeApp
  • Non-strings, empty values and oversized input are refusedServer
  • Older companies with a string counter still bill, one step at a timeServer + database
  • One IP cycling through accounts still trips the IP bucketServer
  • Only the owner changes roles, and ADMIN is a valid roleServer + database
  • Only the owner deletes business recordsServer + database
  • Only the server moves the bill number, and it does not block admin editsServer + database
  • Outsiders and anonymous clients cannot read collection dataServer + database
  • Owners and admins set reorder levels; staff cannotServer + database
  • Path traversal out of the company prefix is refusedServer
  • Payment orders are readable by their own company and never written by a clientServer + database
  • People note their own app version, and still cannot change their roleServer + database
  • Products are created only through the server; only the owner adds groupsServer + database
  • Raw machinery never reaches the user a bare error code is not shown as if it were a sentenceApp
  • Raw machinery never reaches the user a device file path is never shownApp
  • Raw machinery never reaches the user a Firestore index error hides the console URL and project idApp
  • Raw machinery never reaches the user a long dump is not shownApp
  • Raw machinery never reaches the user a package URI is never shownApp
  • Raw machinery never reaches the user a stack trace is never shownApp
  • Raw machinery never reaches the user an unmatched bracket tag is not shownApp
  • Raw machinery never reaches the user an unrecognised internal error falls back to the generic messageApp
  • Real concurrent duplicate requests create exactly one receiptServer + database
  • Real concurrent transactions cannot over-allocate a customer balanceServer + database
  • Repeat offenders back off exponentially rather than being locked outServer
  • Same-company owner and staff can read collection plans and eventsServer + database
  • Script-bearing and non-http schemes are refusedServer
  • Server-issued payment receipts cannot be deleted by clientsServer + database
  • Signed-in everyday calls are limited per account, not per shared IPServer
  • Sitting out a cooldown does not reset the escalationServer
  • Sniffed formats are the ones the server will sign forApp
  • Staff can take money on a bill but never take it backServer + database
  • Staff menu shows only their pagesApp
  • Staff record a payment on a sale but cannot move it to another customerServer + database
  • Staff sell to existing customers but never add or change oneServer + database
  • Stock cannot go negativeServer + database
  • Strikes decay after a long clean spell, restoring the base cooldownServer
  • Sync destinations cannot be injected via newly added company fieldsServer + database
  • Sync jobs and leases remain server-onlyServer + database
  • The 1.2.2 app's sale commit (product, counter, line, receipt, movement) still savesServer + database
  • The bill counter moves one bill at a time and can never be resetServer + database
  • The call past the limit is refused with the base cooldownServer
  • The client IP is read from the front of x-forwarded-forServer
  • The client size cap matches the serverApp
  • The refusal tells the caller how long to wait and nothing moreServer
  • Thresholds come from the environment, not the sourceServer
  • Uploads are identified by content, not by file name a RIFF container that is not WebP is refusedApp
  • Uploads are identified by content, not by file name a valid signature on a truncated WebP header does not over-readApp
  • Uploads are identified by content, not by file name an archive is refusedApp
  • Uploads are identified by content, not by file name an executable is refusedApp
  • Uploads are identified by content, not by file name empty and truncated input is refused rather than crashingApp
  • Uploads are identified by content, not by file name HTML is refused however it is namedApp
  • Uploads are identified by content, not by file name real image signatures are recognisedApp
  • Uploads are identified by content, not by file name SVG is refused: it is a document that can carry scriptApp
  • UserRoleFrom accepts lower case, as older invites stored itApp
  • UserRoleFrom an ADMIN profile is no longer read back as STAFFApp
  • UserRoleFrom anything unknown is the least privileged roleApp
  • UserRoleFrom reads the three stored rolesApp
Plans, trial & payments The 7-day trial and its limits, monthly bill allowances, seats, Razorpay payments and moving between plans. 55/55
  • A ₹0 trial counts as a plan for direct writes from older appsServer + database
  • A 12-month plan is not locked out after its first monthServer
  • A billing month is a calendar month in IndiaServer
  • A declined payment surfaces the gateway reasonApp
  • A desktop refusal is not mistaken for the customer cancellingApp
  • A first payment with no history starts nowServer
  • A flat coupon never takes the price under one rupeeServer
  • A forged or replayed signature is refusedServer
  • A genuine signature verifiesServer
  • A lapsed subscription restarts from todayServer
  • A long plan gets a fresh allowance each monthServer + database
  • A new company is offered the free trialApp
  • A paid-but-unverified payment points the customer at supportApp
  • A percent coupon takes its share off, up to the capServer
  • A plan put in force gets its limits, a fresh allowance and no trial capsServer
  • A scheduled smaller plan is switched on when due, and only thenServer + database
  • A smaller plan waits for the bigger one to end; anything else starts nowServer
  • A trial company adds customers and team only through the serverServer + database
  • A used trial is not offered againApp
  • A verified payment reports the plan and its new end dateApp
  • An ended trial says so, and keeps the dataApp
  • An offer is one plan for one term; anything else is not soldServer
  • An order missing its key or amount is refused before checkoutApp
  • Bills used: same month, next month, after a gap, and with no period yetServer
  • Cancelling charges nothing and is not reported as an errorApp
  • Checkout charges the catalogue price for the plan and termServer
  • Checkout never opens when the order could not be createdApp
  • Coupon codes are canonical upper-case idsServer
  • Coupons are refused when off, early, expired, used up or wrong planServer
  • Deleting last month's bill does not give this month a bill backServer + database
  • Expired screen fits 1280.0App
  • Expired screen fits 360.0App
  • Gateway ids are shape-checked before being used as document pathsServer
  • Invites count members and waiting invites against the plan's seatsServer + database
  • Longer terms save what the price list saysApp
  • Mobile falls through to the SDK and never hangs on itApp
  • Nobody in the company deletes a receiptServer + database
  • One trial per company, and only before any planServer
  • Owners and admins set the bill settings; staff cannotServer + database
  • Plan lookup ignores case and rejects anything not on saleServer
  • Plan picker applies a coupon at 1280.0App
  • Plan picker applies a coupon at 360.0App
  • Renewing early keeps the days already paid forServer
  • Savings are against paying monthly, as advertisedServer
  • TargetPlatform.linux is told where it can pay instead of failing silentlyApp
  • TargetPlatform.macOS is told where it can pay instead of failing silentlyApp
  • TargetPlatform.windows is told where it can pay instead of failing silentlyApp
  • The app sends a plan id and never an amountApp
  • The displayed catalogue matches the prices the server chargesApp
  • The owner starts the free trial once, before any planServer + database
  • The price list is pricingplan.md: 4 plans × 1, 3 and 12 monthsServer
  • The top plan has no bill limit, stored as a real numberServer
  • The trial: 7 days, 30 bills that never reset, freeServer
  • The trial's limits are kept by the serverServer + database
  • Trial limits on products, customers, users and featuresServer
Deliveries & tasks Assigning a bill, each step of the delivery, cash collected at the door, and the owner’s one-tap complete. 47/47
  • A bill not yet sent out offers to send itApp
  • A bill out for delivery shows its courier and TrackApp
  • A collection follow-up is assigned, worked and closed with its outcomeServer + database
  • A courier takes the next delivery step at 1440.0App
  • A courier takes the next delivery step at 360.0App
  • A delivery closes even when no name is taken at 1440.0App
  • A delivery closes even when no name is taken at 360.0App
  • A follow-up is closed with what happened at 1440.0App
  • A follow-up is closed with what happened at 360.0App
  • A manager can close an accepted delivery; walk-in bills take no door paymentServer + database
  • A manager completes a delivery nobody stepped through; the courier cannot skipServer + database
  • A promise cannot exceed what is still owed after the paymentServer + database
  • An owner completes a delivery nobody stepped throughApp
  • Handing over records who took it and the money at 1440.0App
  • Handing over records who took it and the money at 360.0App
  • History shows the delivery and its courierApp
  • Notifications reach every device and forget the dead onesServer + database
  • Ordering open deliveries come before finished onesApp
  • Ordering the longest waiting open delivery is firstApp
  • People are notified about work others give them, not work they takeServer + database
  • Reading stored records a delivery is rebuilt from its stored fieldsApp
  • Reading stored records a sparse record does not throwApp
  • Sale carries a delivery optionApp
  • Staff cannot work someone else's deliveryApp
  • Staff still work their own job step by stepApp
  • Staff take their own deliveries; only managers hand them to othersServer + database
  • Status sequence a finished delivery cannot move anywhereApp
  • Status sequence an open delivery can always be cancelledApp
  • Status sequence an owner or admin can complete any delivery underwayApp
  • Status sequence an unknown stored value reads as assignedApp
  • Status sequence delivered and cancelled both count as finishedApp
  • Status sequence each step is read back from what the server storesApp
  • Status sequence re-applying the current status is a no-op, not an errorApp
  • Status sequence the courier must accept before completing; a manager need notApp
  • Status sequence the courier takes every step in orderApp
  • Task queue a follow-up reads its steps back from the server recordApp
  • Task queue a notification can only open a task screenApp
  • Task queue new work comes first, then the longest waitingApp
  • The courier works through every step in order, then collects at the doorServer + database
  • The staff dashboard shows their figures and tasks at 1440.0App
  • The staff dashboard shows their figures and tasks at 360.0App
  • The staff dashboard shows their figures and tasks at 768.0App
  • Waiting time a delivery with no date does not report waiting timeApp
  • Waiting time a finished delivery is never counted as waitingApp
  • Waiting time an open delivery counts the days since the billApp
  • Whose queue a courier only sees their own jobs as theirsApp
  • Whose queue with no signed-in user nothing is claimed as mineApp
Stock & planning Stock in and out, average cost, low-stock alerts and the loading list. 41/41
  • A delivery needs only quantity and costApp
  • A line deleted before it was costed never takes unitsServer + database
  • A new app's sale on a product with no ledger yet counts stock before the saleServer + database
  • A new sale line is costed from the ledger and reversed exactly onceServer + database
  • A sale takes units out at the average costServer
  • Admin adds stock but cannot create, edit or delete productsApp
  • All stock screens fit 1440.0App
  • All stock screens fit 360.0App
  • All stock screens fit 768.0App
  • An empty ledger costs nothingServer
  • An old app's sale starts the ledger from the product it already updatedServer + database
  • Building the board an untracked product is listed but raises no alertApp
  • Building the board missing stock fields read as zero rather than throwingApp
  • Building the board the loading list covers only what is shortApp
  • Building the board the most urgent products sort firstApp
  • How much to load a large shortfall takes as many packs as neededApp
  • How much to load nothing is suggested for a product that is not lowApp
  • How much to load suggestions round up to whole packsApp
  • How much to load the suggestion refills back to the alert levelApp
  • Inventory filters narrow the listApp
  • Inventory reports unit cost from investment, not sale priceApp
  • Low-stock alerts a product below its alert level is flaggedApp
  • Low-stock alerts a product with no alert level set never nagsApp
  • Low-stock alerts stock at the alert level is not yet lowApp
  • Low-stock alerts zero stock reads as out of stockApp
  • Only owners and admins add stock, and a retry lands onceServer + database
  • Only the owner creates products, with the cost kept privateServer + database
  • Overselling never produces negative stock or costServer
  • Owners and admins may look up a cost record that does not exist yetServer + database
  • Paise arithmetic does not drift over many salesServer
  • Reversing a sale restores exactly what it tookServer
  • Selling the last unit leaves no cost behindServer
  • Staff cannot read any cost; owners and admins canServer + database
  • Staff see price and availability, never cost, at 1440.0App
  • Staff see price and availability, never cost, at 360.0App
  • Stock entry averages cost from what was paidApp
  • Stock forms remain usable with enlarged phone textApp
  • Stock in adds units and what was paidServer
  • Stock validation and save prevent duplicate submissionsApp
  • The migration seeds ledgers and history once, and strips only when switched offServer + database
  • Unless a migration switches it on, no client can write cost anywhereServer + database
Alerts, emails & support Phone alerts, the daily or weekly summary email, and problem reports reaching us. 46/46
  • A bill of several lines is one alert with the whole totalServer
  • A business name cannot break out of the sender headerServer
  • A busy provider is retryable, a rejected address is notServer
  • A company is only emailed when it asked to beServer
  • A daily summary covers the whole of yesterday, in India timeServer
  • A day with nothing in it says soServer
  • A deleted sign-in account falls back to the company's signup emailServer
  • A delivery alerts once, when it becomes delivered, with any cashServer
  • A line with no bill reference is its own billServer
  • A weekly email names the week and dates each entryServer
  • A weekly summary covers the last whole Sunday to SaturdayServer
  • Alert keys are safe document idsServer
  • An account with no email address is told so plainlyServer
  • An address on the profile wins, should one ever be storedServer
  • An email needs a recipient, a sender and a subjectServer
  • An unknown frequency is refusedServer
  • Another company's owner is never picked upServer
  • Assignment alerts ignore self-assignment and unrelated editsServer
  • Customer and product names cannot inject markup into the emailServer
  • Delivery art survives busy and fast step changes; repeat taps are blockedApp
  • Each owner sees their own business name on the verified addressServer
  • Feedback needs a kind and a few words, and is trimmedServer
  • Money reads the way the app writes itServer
  • Obvious rubbish never reaches the email providerServer
  • Owners and admins are told; staff and the person who did it are notServer
  • Payments and expenses say how much, what for and whoServer
  • Push batches devices, targets task channel and only removes dead tokensServer
  • Reduced motion paints a complete delivery step immediatelyApp
  • Reminder days are counted in India's calendar, whatever the hourServer
  • Renewal reminders say when access ends and what to payServer
  • Sale lines are grouped into the bills they were written asServer
  • Slow requests show a spinner only after the story finishesApp
  • Staff are never sent the owner's summaryServer
  • Staff cannot switch the owner's summary onServer
  • Switching it off also clears the last errorServer
  • The 9am job runs through a company it cannot email without crashingServer
  • The day's figures add upServer
  • The email names the business, the money and who billed itServer
  • The owner can choose a weekly summaryServer
  • The owner is found by their sign-in email, which profiles never storeServer
  • The owner switches the daily summary on with their sign-in emailServer
  • The setup check reports domains, and names a bad key plainlyServer
  • The summary reads sales the way the live index runs, oldest first outServer
  • The support email names who sent it and escapes what they wroteServer
  • Top alerts queue, coalesce duplicates, preserve actions and fit mobileApp
  • Weekly owners are only attempted on Sunday morningsServer
Google Sheets & exports Rows reach the owner’s Google Sheet in order, retries never duplicate, and statements export correctly. 25/25
  • A lost successful write response can be retried without duplicate rowsServer
  • A newer job is not overwritten by an older job's failureServer
  • A payment over several bills says which bills it paidApp
  • A weekly summary names its Sunday send and whole weekApp
  • A workbook lease prevents another writer and releases after failureServer
  • An edit queued during a write is not lost when that write completesServer
  • An owner already receiving it can preview oneApp
  • Business text is RAW, preserving formulas as text and leading zerosServer
  • Connected status and repair controls fit width 1100.0App
  • Connected status and repair controls fit width 360.0App
  • Corrections from bill edits are marked and reduce paymentsApp
  • Deleted and foreign-company records clear only the queued row IDServer
  • Deletion clears all matching rows, preserving unrelated recordsServer
  • Disconnected owner sees setup, not repair actionsApp
  • Failures remain queued with backoff and are never dropped after five attemptsServer
  • IDs are read from their schema column, not from a user's added noteServer
  • New records reuse empty managed rows and never overwrite unrelated dataServer
  • Queue IDs separate companies and avoid concatenation collisionsServer
  • Staff cannot access workbook controlsApp
  • Statement and list PDFs render, including long listsApp
  • Statement merges bills and payments with a running balanceApp
  • The owner can switch the daily summary email onApp
  • Unreadable sheets never trigger blind writesServer
  • Updates the existing record and clears duplicate IDsServer
  • Worker reads the current source record, then acknowledges its generationServer
Offline & updates Bills made without internet wait safely and sync once; app updates are offered and installed correctly. 29/29
  • A network failure is retried, then waits after five attemptsApp
  • A refused bill is kept for the person, never droppedApp
  • A refused secondary stream never blocks the primary oneApp
  • An old per-device queue of bill lines becomes whole billsApp
  • Each person has their own queueApp
  • Nobody signed in: nothing is read, queued or sentApp
  • Pairs the latest of both, starting with the initial secondApp
  • ParseInstalledVersion reads name and build reported separately (Android)App
  • ParseInstalledVersion reads Windows "1.2.2+1006" with no build numberApp
  • ParseInstalledVersion reads Windows four-part "1.2.2.1006"App
  • ParseInstalledVersion unknown build is 0, never blocks anyoneApp
  • Primary errors still reach the listenerApp
  • ReleaseManifest only newer builds count as an updateApp
  • ReleaseManifest rejects a malformed hash or missing buildApp
  • ReleaseManifest rejects downloads from another hostApp
  • ReleaseManifest rejects plain http downloadsApp
  • ReleaseManifest resolves file links next to latest.jsonApp
  • Results decode to plain Dart valuesApp
  • Server status maps to the plugin error codeApp
  • Someone signing in mid-replay neither sends nor receives the first person's billsApp
  • The person can discard one itemApp
  • The same bill queued twice is kept onceApp
  • UpdateController a quiet check hides server errorsApp
  • UpdateController finds a newer releaseApp
  • UpdateController is unavailable with no manifest URLApp
  • UpdateController is up to date on the same buildApp
  • UpdateService.download deletes a file whose hash does not matchApp
  • UpdateService.download keeps a file whose size and hash matchApp
  • UpdateService.download stops a download that is bigger than announcedApp
App screens Screens draw correctly on phones and laptops, with large text, for every role. 42/42
  • A problem report reaches support with the app versionApp
  • A smaller plan waits for the current one smaller plans start when the paid plan ends; bigger ones nowApp
  • A smaller plan waits for the current one the order summary says soApp
  • Basic widget smoke testApp
  • Bill settings save only when something changedApp
  • Dashboard and inventory adapt at 1440.0App
  • Dashboard and inventory adapt at 360.0App
  • Dashboard and inventory adapt at 768.0App
  • Dashboard failure never presents zero sales as real dataApp
  • Date presets and clear update the rangeApp
  • Direct link back falls back to Overview on desktopApp
  • Expense type cards total entries and filter historyApp
  • More opens /collections and Back restores the entry pageApp
  • More opens /customer-prices and Back restores the entry pageApp
  • More opens /deliveries and Back restores the entry pageApp
  • More opens /expenses and Back restores the entry pageApp
  • More opens /profit-loss and Back restores the entry pageApp
  • More opens /reports and Back restores the entry pageApp
  • More opens /settings and Back restores the entry pageApp
  • More opens /stock-planning and Back restores the entry pageApp
  • More opens /transaction-history and Back restores the entry pageApp
  • More opens /wholesalers and Back restores the entry pageApp
  • More switches to the /products tab without a back arrowApp
  • More switches to the /sales-history tab without a back arrowApp
  • Operational pages fit at 1440.0App
  • Operational pages fit at 360.0App
  • Operational pages fit at 768.0App
  • Owners and admins keep adding and editingApp
  • Profile edit action opens fields and cancel restores viewApp
  • Sales dates live inside filters and custom opens calendarApp
  • Screen names never carry record idsApp
  • Settings pages fit 1440.0App
  • Settings pages fit 360.0App
  • Settings pages fit 768.0App
  • Staff see their own account, sync and help onlyApp
  • Staff see their own profile, not the teamApp
  • Staff take payments but cannot add or edit customers or billsApp
  • System Back returns from a direct mobile routeApp
  • Team search and existing role filters workApp
  • Text size is chosen per deviceApp
  • The owner sees every section at 1280.0App
  • The owner sees every section at 360.0App

What these cover, and what they don’t

These tests check RYBO’s own logic and screens: sums, rules, permissions, sign-in, syncing and what each role can see. They run on a computer before each release, against a local copy of the database — never your real data.

They don’t replace testing on real phones and networks, which we also do before a release. Found something that isn’t right? Tell us in the app: Settings → Help → Report a problem, or email skrohanparveag@gmail.com.