# Kaabe SaaS POS 1.0.0 — Testing report

**Environment (isolated, test data only):** Ubuntu 24.04, PHP 8.3.6 (mod_php, same extensions as cPanel EA-PHP 8.3), MariaDB 10.11.14,
Apache 2.4 with HTTPS (self-signed wildcard certificate), a cPanel-like account (`/home/kaabeuser/kaabe`, Apache running as that user),
`admin.kaabe.test` → `platform/public`, `*.kaabe.test` → `pos`, cron simulated by `php artisan schedule:run` every minute.
Test businesses: **Alpha** (Professional), **Beta** (Basic → Professional), **Gamma** (Free), **Delta** (Basic), **Epsilon/Zeta**
(provisioned through the cPanel API simulator), **deeqdemo** (adopted legacy copy). Databases: `kaabetest_*` only.
`ssgplatforms_deeq`, the InMotion server, production credentials and DNS were **never** accessed.

Every result below was produced by executing the software. The integration scripts (Python, HTTP-level, verifying database state after every operation) were run in the test
environment and are not shipped in the package, because they contain throw-away test passwords. Automated: PHPUnit **55 tests / 527 assertions — PASS**; `php -l` on
**4,950** PHP files — PASS except one vendor file never loaded on PHP ≥ 5.1.2 (`htmlpurifier/…/HTMLPurifier.autoload-legacy.php`);
`bash -n` on every script — PASS.

---

## PASS — Executed successfully

### Installation, migrations, Super Admin, 2FA
- `scripts/check-requirements.php` (no FAIL); `artisan key:generate`; `kaabe:install` (11 central migrations, seeders, first Super Admin, registry); re-run refused by the install lock.
- Super Admin sign-in; wrong password rejected; logout; 16 admin pages render.
- 2FA: setup forced at first login, wrong code rejected (setup + challenge), valid TOTP accepted, every later login challenged, pages blocked until verified, 2FA endpoints throttled (429).
- Staff roles: a *Support Agent* is refused (403) on Settings, Staff, Roles, Plans, Provisioning and on a direct POST that suspends a business.

### Businesses, plans, subscriptions, payments
- Provisioning from the web form (Alpha/Beta/Gamma/Delta): own database + own DB user (privileges on that database only), templates, owner, branch, plan, entitlements, registry; auto-activation.
- Failure → *Retry* resumes, completed steps not repeated; duplicate and reserved sub-domains refused.
- Suspension blocks the POS **immediately** (existing sessions, login page, API) with the reason shown; other businesses unaffected; reactivation restores access immediately.
- Manual payment recorded; negative amount refused; extend by 1 month; upgrade Basic → Professional; downgrade blocked when usage exceeds the smaller plan (lists what is over); subscription suspend/resume; history recorded.
- Limit/module exceptions (overrides) delivered to the POS and effective; removing them restores the plan.

### Branches, warehouses, users, roles, permissions
- Branch add/edit/suspend/reactivate; stored only in the business's own database.
- Warehouse created from the POS location form; sales blocked at a warehouse (web → no-access page; API → 403 with message); receiving stock into a warehouse allowed; the creator keeps access to new locations.
- Users: temporary password shown once, forced change at first POS sign-in, bcrypt storage, old temporary password rejected; duplicate username refused; a location id from another business refused.
- Cashier: 2 module permissions vs owner 18; blocked from Store Config and Employees.

### Everyday POS flows (database verified after each step)
- Products: create (API, web, CSV import), search/suggest, view, edit, clone (confirmed POST).
- Customers and suppliers: create, view, edit, search; deleting one customer by id.
- Purchasing: receiving adds stock (10 → 30); branch-level stock independent (branch 2 +10/−2, branch 1 unchanged).
- Sales: API and web checkout (add item, payment, complete); stock decremented; receipt page shows items and totals.
- Returns/refunds: API and web return create a negative sale and put stock back.
- Registers: opening float required when cash tracking is on; close-out shows expected cash; log records open 100 / sales 55 / close 155; register-log report.
- Reports: dashboard, summary sales, detailed sales, categories, payments, receivings, profit & loss, inventory summary/low, register log.
- Crawl: 260 pages (Alpha owner) and 150 pages (Gamma owner) without HTTP 500 or PHP error.

### Module gates — Free vs Basic vs Professional (backend AND menu)
Appointments, Work Orders, Deliveries, Messages, Price Rules, Gift cards/Loyalty, Time Clock (also with the store setting switched on), Commissions report, Advanced analytics (P&L, register log), Purchasing, Expenses, Invoices: **78 checks, 0 open failures** (two first-run failures were wrong test URLs/expectations, corrected and re-run) — hidden from the menu AND refused by URL when not in the plan, shown and working when included.
API: per-module refusal (`module_not_in_plan`) for appointments, deliveries, price rules, gift cards on Basic; allowed on Professional; a user API key stops working when the API module is removed and works again when restored.
Settings: loyalty, time clock, commission rate and e-commerce platform refused via Store Config on Free, saved on Professional.

### Plan limits (web, API, import)
Branches (Free 1, Pro 3; also inside the POS), warehouses (Free 0, Pro 2), users (Free 2), products (web form, API bulk: exactly 2 of 3 created with 2 free slots; CSV import all-or-nothing with a clear error, 3/3 when within the limit), registers (Free 1), storage (small upload accepted, 4 MB refused when ~2 MB left), API keys (Free 0; **Professional #1 accepted, #2 accepted, #3 refused**, also when POSTed directly).
No existing data was deleted to make a limit test pass (limits were set per business with exceptions).

### Tenant isolation
Alpha/Beta/Gamma: products, customers, sales and locations never appear in another business (databases, web search, API lists, item/receipt URLs with another business's ids); Alpha API key refused on Beta and Gamma; each business DB user has privileges only on its own database (verified with SHOW GRANTS); unknown sub-domain, bare domain and spoofed host (`alpha.kaabe.test.evil.com`) → 404.

### API authentication
No key / invalid key → 403; valid key → 200; keys stored hashed; suspended business → API blocked.

### Sales sync
Normal and incremental sync; 6 sales in the same second (concurrent) each counted once; overlapping and repeated syncs → no duplicates (unique (business, branch, day)); **one sale = one aggregate**; sync never changes the business's sales tables (checksums identical); voided sale removed; edited historical sale updated; a sale moved to another day moves its aggregate (old day decremented); per-branch and business totals equal the business database; central dashboard and per-business drill-down.
Failures (undecryptable stored secret, wrong DB password): 3 failed runs recorded (none stuck "running"), no password in errors, alert raised, other businesses keep syncing, recovery catches up missed sales, alert auto-resolved.

### Backup / restore / rollback
- `scripts/backup-all.sh` (as the cPanel user): central + every business database + config + registry; `sha256sum -c` OK; files mode 0600.
- Restore into a fresh database: identical to live (m7 profile compare: counts and checksums).
- Disaster restore in place (items and sales deleted → business suspended → restored with its own DB user → reactivated → reconcile): all rows back, POS works, central totals reconciled.
- Rollback of an adoption (rehearsal): restored copy equals the pre-adoption original on **54/54** compared measures.

### Existing POS adoption (COPY only)
Legacy database built from the original `pos.zip` schema (MD5 passwords, a password-less user, sales, purchases, register log): `kaabe:rehearse` validation PASS — counts identical for products, customers, suppliers, users, locations, registers, stock, sales, sale lines, payments, purchases; totals identical (sales, profit, stock units, balances, per-year and per-day); password hashes unchanged; the password-less user identified (1 of 1); only Kaabe objects added; tenant isolation 3/0; rollback PASS.
Demo adoption (`kaabe:adopt --demo-copy`, Free plan): counts unchanged, users limit exception recorded (3 > 2), Kaabe tables added, registry entry, owner mapped; legacy MD5 password signs in and is upgraded to bcrypt; password-less user cannot sign in.
Guards: the name `ssgplatforms_deeq` is refused without the approval phrase (no connection attempted); `adopt-existing.sh` refuses without `PHASE 3 APPROVED — PROCEED`.

### cPanel provisioning (local cPanel UAPI simulator)
Database, database user and privilege assignment through `Mysql::*`; sub-domain through `SubDomain::addsubdomain` with the POS document root; `SSL::start_autossl_check`; missing MySQL feature → failed with a clear error; HTTP 500 → fails safely, *Retry* completes; invalid token (401) → nothing created, retry after fixing succeeds; each database created exactly once; re-provisioning a live business changes nothing; no token/password in errors or logs; owner reaches the POS.

### Security regression (after all fixes)
CSRF (admin 419, POS 403, confirm-page POST required for state-changing links; a 295-page scan showed no data change from GET); session id rotated at login (both apps); cookies Secure + HttpOnly + SameSite (both apps); HTTP → HTTPS 301 (both apps); CSP, HSTS, X-Frame-Options, nosniff, Referrer-Policy (admin), HSTS/X-Frame/nosniff/Permissions-Policy (POS); bcrypt passwords; temporary passwords must be changed; login rate limiting (admin 429 after 10/min; POS locks after 5 failures); 2FA; roles/permissions; tenant isolation; API authentication; suspended-business blocking; host-header validation; secret scan (no production password, test credentials, `.env`, dumps or registry in the package; only vendor documentation examples).

### Final ZIP smoke test
See the section at the end (executed from the actual ZIP in a new account).

---

## FAIL — Executed but failed
None open. Every failure found was fixed and re-tested (see CHANGELOG.md "1.0.0"). Test-harness mistakes (e.g. an automatic form fill that wrote wrong store settings) were corrected in the harness, not hidden.

## NOT EXECUTED
- Real card/EMV terminals, SMS/e-mail delivery, QuickBooks and e-commerce channel synchronisation with real external accounts (no accounts/devices in the test environment; plan gating of these settings was tested).
- Load testing beyond concurrent-sale tests.

## REQUIRES INMOTION VERIFICATION
- PHP 8.3 selected for both domains in *MultiPHP Manager*; extensions present; `upload_max_filesize ≥ 20M`, `post_max_size ≥ 25M`, OPcache `revalidate_freq ≤ 2`.
- MariaDB ≥ 10.6 on the account.
- Real cPanel API token and its features (MySQL Databases, Subdomains, AutoSSL); database/user quotas; wildcard DNS or per-business sub-domains; AutoSSL certificates.
- Cron every minute allowed on the plan.
- *Force HTTPS Redirect* on both domains; SSL certificate for `admin.` and business sub-domains.
- Backups copied off the server (cPanel Backup / remote storage).

---

## Final ZIP smoke test (executed)

Fresh cPanel-like account `smokeuser` (new home folder, new databases `smokeuser_*`, new domain `*.smoke.test`, Apache running as that
user, own cron). The **actual `kaabe-saas-1.0.0.zip`** was extracted; the working directory was not used. Installation followed
`INSTALLATION.md` exactly (copy both `.env.example`, keys, `check-requirements.php`, `key:generate`, `kaabe:install`, `config/route/view:cache`).
No Composer and no internet were used — the bundled `platform/vendor` worked.

| Step | Result |
|---|---|
| Requirements check | PASS (no FAIL; WARN only for the 2M/8M PHP upload defaults) |
| `kaabe:install` (migrations, plans, Super Admin, registry, lock) | PASS |
| Super Admin login + forced 2FA setup | PASS |
| Business created from the web form, provisioned by cron/queue, active | PASS |
| Owner POS login after forced password change | PASS |
| Product created in the POS; real web sale (3.00) recorded | PASS |
| Central sales aggregation equals the POS (3 sales, 3.00) | PASS |
| Suspension blocks the POS immediately (403) | PASS |
| Reactivation restores access | PASS |
| HTTP → HTTPS redirect | PASS |

Only `TESTING-REPORT.md` (this section) was added to the package after the smoke test; no code changed.

---

## Staging-preparation changes (29 Sep 2026) — executed

| Test | Result |
|---|---|
| `scripts/backup-database.sh` on a legacy POS copy (165 tables) | PASS — VERIFIED (gzip, SHA-256, completion marker, 165/165 tables); files 0600 |
| Same with a wrong password | PASS — exits non-zero, nothing reported as verified |
| The verified dump restores into an empty database | PASS — items 3/3, sales 2/2, employees 3/3, customers 1/1 |
| `adopt-existing.sh` without the phrase / with a hyphen instead of the em dash | PASS — "Refused", exit 3, nothing done |
| `kaabe:adopt` on `ssgplatforms_deeq` / `SSGPLATFORMS_DEEQ` without the exact phrase | PASS — refused, no business created |
| `bash -n` on all scripts | PASS |
| Application code | unchanged since the smoke-tested build (file hashes compared; only documentation and the two scripts above differ) |

`adopt-existing.sh` was **not** run end-to-end: it requires the production approval phrase. Its steps
(backup, `m7_profile.php`, `kaabe:adopt`, `m7_compare.php`, rollback) were each executed on copies.
