Test chart storage
Test ChartStorageAdapter through the public chart controller against a
production-shaped backend. A chart-storage integration is complete only when
each enabled object survives a clean reload and cannot cross an unauthorized
user or workspace boundary.
Start with capabilities
Inspect the mounted chart before testing UI:
const capabilities = widget.chart().getStorageCapabilities();
console.table(capabilities);
Every true value must correspond to a callable adapter method. Unsupported
methods must stay absent. Feature settings may hide a supported control, but
they must not imply backend support.
Run the chart-storage contract matrix
| Object family | Required success path | Missing result | Additional failure proof |
|---|---|---|---|
| Chart workspace layout | Create, list, load, update the same backend id, delete | loadLayout returns null | Create without an assigned id fails; update does not duplicate |
| Drawings | Save, load, delete through tombstones, save a new revision | loadDrawings returns null | Stale baseRevision preserves local and remote state |
| Template | Save, list, load, apply, overwrite, delete | loadTemplate returns null | Drawing templates remain isolated by tool and id |
| User settings | Load before ready, change, autosave, reload, delete keys | loadUserSettings returns null | Overlapping saves cannot restore an older value |
| Replay state | Save cursor and speed, reload paused at the same cursor | loadReplayState returns null | Invalid bar indices reject without changing live state |
| Chart image upload | Upload, save rewritten parent JSON, restore from the hosted URL | Not applicable | Upload failure prevents the parent JSON save |
Use millisecond timestamps throughout. Ten-digit Unix-second values make chart workspace layout sorting and freshness displays incorrect.
Prove chart workspace layout identity
Exercise the canonical chart workspace controller:
saveAs('opening-drive', 'Opening drive')creates a record and returns a logicallayoutIdplus a backendstorageId.list()returns the saved chart workspace layout record.- Change the symbol, interval, indicator stack, and drawing set, then load the chart workspace layout and verify the complete workspace returns.
- Save again and confirm the backend updates the original
storageId. - Reload the application, rebuild or restore the logical-to-storage mapping, repeat the update, and confirm no duplicate record appears.
- Delete the chart workspace layout and confirm both list and load report absence.
Run the same sequence for a single chart and a multi-chart workspace. A
chart-level saveLayout() test cannot prove multi-chart workspace persistence.
In the multi-chart case, remount with the same stable chart ids and confirm each
saved entry reaches its matching controller. Also load a saved entry whose chart
id is not mounted and confirm it is skipped rather than silently applied to a
different chart.
Prove isolation and authorization
For every enabled chart-storage object family, test these changes independently:
- user A to user B;
- workspace A to workspace B;
- one chart workspace layout or chart id to another;
- one drawing sharing mode to another;
- one drawing-template tool to another.
An unauthorized request must return a permission failure, never another
tenant's data and never null disguised as absence. Repeat with an expired
credential and a valid credential lacking the required scope.
Prove drawing concurrency
- Load the same separate drawing bucket in two clients.
- Save from client A and retain the returned revision.
- Save the stale copy from client B with its old
baseRevision. - Confirm the backend returns a conflict containing
remoteRevisionand the currentremoteState. - Resolve separate conflicts with
resolveDrawingSharingConflict('reload-server')andresolveDrawingSharingConflict('keep-mine'); verify the server copy wins in the first case and the local copy is force-saved in the second. - Separately call
loadDrawings({ applicationMode: 'merge' })with non-overlapping edits and confirm both sides remain. Repeat with edits to the same drawing and confirm they enter conflict state instead of being treated as a third resolution choice. - Confirm a pushed update, when supported, reaches only the matching bucket.
For a custom subscribeDrawings, count open sockets, timers, and listeners.
After a chart context change and after chart destruction, every count must
return to zero and later server events must invoke no callback.
Verify chart image uploads
- Add an image drawing whose source is a local
data:URL. - Save each supported parent: a chart workspace layout, a separate drawing record, and a drawing template.
- Confirm the file service receives the upload before the parent JSON save.
- Confirm saved JSON contains the hosted URL and no inline image bytes.
- Reload in a clean browser session and verify the image renders.
- Reject an invalid or oversized upload and confirm no parent JSON is saved.
Exercise transport failures
Test the selected chart storage adapter with:
- an empty backend and a genuine missing record;
- malformed JSON and a structurally invalid public object;
401,403,409,429, and5xxresponses;- a successful empty delete response;
- connection loss before a response and after the server may have written;
- slow responses arriving after chart context has changed.
The built-in REST adapter does not add retries, timeouts, or cancellation. If a
product requires them, wrap fetch and define whether an uncertain write is
reconciled before retrying.
Finish with a browser smoke test
- Mount with the same chart-storage and feature settings used in production.
- Save and load chart state through the built-in storage UI, not only through direct adapter calls.
- Confirm only supported rows are visible and denied permissions remain clear.
- Reload in a clean browser context and restore each production object family.
- Check narrow desktop and mobile widths for readable dialogs and keyboard focus order.
- Destroy the chart and verify no chart-storage request, autosave, or subscription fires afterward.
Troubleshoot by boundary
| Symptom | First check |
|---|---|
| Chart-storage row is missing | getStorageCapabilities() and the matching feature setting |
| Every chart workspace layout save creates a duplicate | Create response id and retained ref.storageId mapping |
| Chart state disappears after reload | Adapter scope, browser storage key, or in-memory fallback |
| One tenant sees another tenant's data | Server-side identity derivation and database key |
| Drawings reappear after deletion | removed and removedGroups tombstones |
| Replay state works locally but not through REST | GET and POST /v1/replay, the layout wire query, and the returned ReplayState |
| An image restores as a broken URL | Upload authorization, URL lifetime, and saved JSON rewrite |
Next steps
- Chart storage — confirm the selected local, REST, or custom path.
- REST chart storage adapter — check exact wire shapes and response rules.
- Custom chart storage adapter — inspect method, identity, and subscription ownership.
- Chart image storage — verify upload and file-lifecycle responsibilities.
- Storage UI — verify which controls each chart-storage capability exposes.