refactor(shared): add UPLOAD_TRANSPORT injection token (RB-25)

UploadShellService injected the concrete KeepaliveTransport class instead
of a token. The class was not exported, so a spec could not fake it, and
could not provide against the UploadTransport interface either, since an
interface is not a DI token. The port existed only on paper.

Add UPLOAD_TRANSPORT, an InjectionToken with a default factory that
resolves the same KeepaliveTransport singleton, following the
SessionPort/SESSION_PORT shape. UploadShellService now injects the token.
Runtime behaviour is unchanged.

Add upload-shell.service.spec.ts: a recording fake transport plus a fake
UploadAdapter exercise upload(), delete(), cancel() and pollReturning(),
the four methods the missing seam left unreachable. Coverage for
upload-shell.service.ts goes from 0% to 88.6% line / 85% branch.

Mark RB-25 done in 99-backlog.md and add its implementation note.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
eho
2026-08-28 08:31:34 +02:00
co-authored by Claude Opus 5
parent 693016445b
commit adad4513d0
5 changed files with 401 additions and 4 deletions
+26 -1
View File
@@ -20,7 +20,7 @@ tested where._
Every bullet below is a real test name from the suite — an `it()` title (frontend) or a test
method name (backend), read as a sentence. Nothing here is hand-written prose: this page
**is** the suite, reshaped for a business reader. 467 frontend behaviours across
**is** the suite, reshaped for a business reader. 480 frontend behaviours across
9 contexts; 238 backend behaviours across 41 test
classes.
@@ -675,6 +675,31 @@ classes.
- map only touches Success
- map2 precedence: Failure &gt; Loading &gt; Success
#### UploadShellService.cancel
- calls the transport cancel function for an in-flight upload and forgets it
- is a no-op for a localId with nothing in flight
#### UploadShellService.delete
- dispatches UploadDeleting, then UploadDeleteComplete on success
- dispatches UploadDeleteFailed with the server detail on failure
- falls back to an empty reason when the server sends no detail
#### UploadShellService.pollReturning
- does nothing when there are no uploads to poll
- dispatches BackgroundUploadsReturned for uploads the server reports complete
- does not dispatch when nothing has arrived yet
#### UploadShellService.upload
- dispatches UploadQueued with the transport backgroundSync flag, then sends via the transport
- translates a progress callback into UploadProgress
- translates a resolved transport into UploadComplete
- translates a rejected transport into UploadFailed with the reason
- does not dispatch UploadFailed on a user-initiated abort
#### authGuard
- allows an authenticated user