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.