Skip to content

feat: Data and local storage setup#35

Merged
hsc00 merged 8 commits intomainfrom
feat/data-and-local-storage-setup
Apr 2, 2026
Merged

feat: Data and local storage setup#35
hsc00 merged 8 commits intomainfrom
feat/data-and-local-storage-setup

Conversation

@hsc00
Copy link
Copy Markdown
Owner

@hsc00 hsc00 commented Apr 2, 2026

Overview

This PR implements durable local data persistence for the DAM system, extending the Asset domain model with lifecycle tracking and introducing a complete MySQL repository implementation with local-development mock storage support.

Domain Model Enhancements

The Asset domain model now tracks additional lifecycle state:

  • Added chunkCount (minimum 1) and updatedAt fields to complement existing createdAt
  • Introduced ClockInterface abstraction for time-based operations, with SystemClock providing system time
  • Updated factory methods (createPending, reconstitute, reconstituteUploaded) to accept lifecycle state as structured data rather than individual parameters
  • State transitions (markUploaded, markFailed) now advance updatedAt with monotonic timestamp validation ensuring updatedAt >= createdAt
  • Added public getters for chunkCount and updatedAt

Repository & Persistence

AssetRepositoryInterface extended with:

  • searchByFileName(AccountId $accountId, string $query): array - performs account-scoped, case-insensitive substring matching with results ordered by createdAt descending, then id ascending; returns empty list for blank queries after trimming

MySQLAssetRepository implementation provides:

  • Insert-or-update save semantics with optimistic locking via StaleAssetWriteException on concurrent modifications
  • Query operations: findById, findByUploadId, and searchByFileName with prepared statements
  • Immutable field protection and state-transition validation
  • Deterministic datetime handling with microsecond precision
  • Prepared statement LIKE pattern escaping with custom escape character

Database Schema (migrations/20260401120000_create_assets_table.sql):

  • assets table with columns for identity (id, upload_id), tenancy (account_id), file metadata, processing state, chunking, and optional completion proof
  • Integrity constraints: status enum, chunk_count >= 1, lifecycle invariants, and upload_id uniqueness
  • Timestamp columns with microsecond resolution and monotonic ordering guarantee

Storage Adapter

MockStorageAdapter provides deterministic local-development upload targets:

  • Returns mock URLs of the form mock://uploads/{uploadId}/chunk/0
  • Supplies completion proof metadata (etag from response header) with fixed expiry
  • Enables local testing without cloud storage dependencies

UploadTarget & URL Validation

UploadTarget.normalizeUrl() enhanced to:

  • Accept deterministic mock URLs (mock://uploads/{uploadId}/chunk/0) for local development
  • Enforce strict mock URL shape validation (no userinfo, port, query, or fragment)
  • Maintain existing HTTPS and localhost/loopback allowances for non-mock schemes
  • Validate uploadId segments against UUID pattern

Testing

Comprehensive test coverage added:

  • AssetsTableBootstrapTest: Verifies schema creation, column types/nullability, indexes, and constraint enforcement
  • MySQLAssetRepositoryTest: Integration tests covering CRUD operations, concurrent write protection, state transitions, search semantics, and error cases
  • MockStorageAdapterTest: Unit tests verifying deterministic URL generation and completion proof metadata
  • AssetTest: Extended with lifecycle field validation, invalid chunk count detection, and timestamp ordering assertions
  • UploadTargetTest: Expanded mock URL shape validation covering path structure, segment validation, and malformed URL rejection

Helper utilities:

  • CompareAndSwapRacePdo and CompareAndSwapRaceStatement for simulating concurrent write races in integration tests

Documentation

  • ADR 04: Updated Asset persistence contract documenting lifecycle field requirements and searchByFileName semantics
  • API Schema: Clarified UploadTarget.url to specify HTTPS for production and deterministic mock form for local development
  • Implementation Log (US-03): Comprehensive documentation of delivered durable local data work, affected files, test outcomes, and follow-up wiring tasks
  • mkdocs.yml: Added navigation entry for the implementation log

@hsc00 hsc00 linked an issue Apr 2, 2026 that may be closed by this pull request
9 tasks
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

Warning

Rate limit exceeded

@hsc00 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 51 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 3 minutes and 51 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 740dea63-718b-468b-a7a7-772e823387a6

📥 Commits

Reviewing files that changed from the base of the PR and between 7c1d4b2 and cfa2879.

📒 Files selected for processing (3)
  • src/Infrastructure/Persistence/MySQLAssetRepository.php
  • tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php
  • tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php
📝 Walkthrough

Walkthrough

This PR extends the Asset domain model with lifecycle tracking (chunkCount, updatedAt), implements a MySQL repository with account-scoped file-name search and compare-and-swap write protection, adds a mock storage adapter for local development, includes database migrations with lifecycle invariant constraints, and provides comprehensive integration and unit tests.

Changes

Cohort / File(s) Summary
Domain Model & Lifecycle
src/Domain/Asset/Asset.php, src/Domain/Asset/ClockInterface.php, src/Domain/Asset/SystemClock.php
Extended Asset to track chunkCount and updatedAt via ClockInterface; updated factory methods to accept persisted state array; added initializeLifecycleState() validation; state transitions now advance updatedAt; new public getters for lifecycle fields.
Domain Contracts & Exceptions
src/Domain/Asset/AssetRepositoryInterface.php, src/Domain/Asset/Exception/StaleAssetWriteException.php, src/Domain/Asset/ValueObject/UploadId.php, src/Domain/Asset/ValueObject/UploadTarget.php
Added searchByFileName() method to repository interface; introduced StaleAssetWriteException for write-conflict detection; refactored UploadTarget::normalizeUrl() to validate mock scheme (mock://uploads/{uploadId}/chunk/0) with strict path/segment rules and improved transport-security error messages; extracted UploadId::isValid() validator.
MySQL Persistence
src/Infrastructure/Persistence/MySQLAssetRepository.php, migrations/20260401120000_create_assets_table.sql
New repository implementation with read-before-write compare-and-swap pattern, safe-update assertions, idempotent saves, and searchByFileName() with case-insensitive LIKE matching, deterministic ordering, and blank-input handling; SQL migration defines assets table with lifecycle fields, lifecycle state constraints (status enum, chunk_count >= 1, completion-proof presence rules), timestamp ordering (updated_at >= created_at), and unique upload_id.
Mock Storage Adapter
src/Infrastructure/Storage/MockStorageAdapter.php
New adapter returning deterministic local mock URLs (mock://uploads/{uploadId}/chunk/0), fixed completion-proof metadata (etag from response header), and a far-future expiry for local development.
Documentation & Navigation
docs/adr/04-asset-domain-contracts.md, docs/api/01-agree-api-schema.md, docs/logs/03-data-local-storage-setup.md, mkdocs.yml
Updated ADR with lifecycle field persistence, invalid-state rules, and searchByFileName() contract; API docs distinguish HTTPS production URLs from deterministic mock URLs with client-side validation rules; new implementation log documenting delivered durable-local-data work including schema, repository semantics, and mock adapter; navigation updated to include new log page.
Integration Test Infrastructure
tests/Integration/Infrastructure/Persistence/BaseAssetsTableTestCase.php, tests/Integration/Support/CompareAndSwapRacePdo.php, tests/Integration/Support/CompareAndSwapRaceStatement.php
Added abstract test base with MySQL connection bootstrapping, temporary schema helpers, constraint validation utilities, and lifecycle-state builders; introduced CompareAndSwapRacePdo and CompareAndSwapRaceStatement for simulating concurrent write races in tests.
Repository Integration Tests
tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php, tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php, tests/Integration/Infrastructure/Persistence/AssetsTableLifecycleTest.php
End-to-end repository tests validating CRUD, state transitions, idempotency, immutability, stale-write rejection, upload-ID uniqueness, and file-name search semantics; schema test verifies table structure and reapplicability; lifecycle test validates constraint enforcement across pending/failed/uploaded states.
Unit Tests
tests/Unit/Domain/Asset/AssetTest.php, tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php, tests/Unit/Infrastructure/Storage/MockStorageAdapterTest.php
Expanded Asset tests to cover lifecycle getters, defaulting of optional persisted fields, invalid chunkCount validation, and updatedAt ordering; refined UploadTarget tests with mock-shape validation and centralized transport-security assertions; new MockStorageAdapter tests verifying deterministic URL generation and metadata consistency.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • PR #34: Extends and modifies the same Asset domain model and repository/storage contracts (Asset.php, AssetRepositoryInterface, UploadTarget/StorageAdapter).
  • PR #30: Extends and modifies the same Domain/Asset types introduced in prior work (Asset.php, AssetRepositoryInterface, UploadTarget handling).

Suggested labels

enhancement

Poem

🐰 Hoppy hop, the data's stored,
In MySQL gardens, well-restored,
Chunks counted, timestamps true,
Search finds assets, old and new!
Mock uploads dance with deterministic flair,
A warren of persistence, oh so fair! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is completely empty and does not follow the provided template. No summary, changes list, testing checklist, or reviewer notes are included. Add a summary of the changes, list affected components, confirm testing status (unit/integration), and note any reviewer concerns or follow-up items.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: Data and local storage setup' clearly aligns with the main changes—implementing durable local data persistence, Asset domain lifecycle fields, repository search, and local mock storage adapter.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/data-and-local-storage-setup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Infrastructure/Persistence/MySQLAssetRepository.php`:
- Around line 32-73: In save() in MySQLAssetRepository, close the TOCTOU window
on the INSERT path by catching duplicate-key PDOException from the
$statement->execute($this->assetParameters($asset)) call; on duplicate-key, call
findById($asset->getId()) again and if the returned entity is non-null and
assetParameters($asset) === assetParameters($persisted) treat the save as
successful (return), otherwise rethrow the exception (or convert to the original
error flow); alternatively implement the insert path as an idempotent upsert
(e.g., INSERT ... ON DUPLICATE KEY UPDATE) that performs a no-op update for
identical rows. Ensure you reference save(), findById(), and assetParameters()
when making these changes and preserve assertSafeUpdate()/updateMutableFields()
behavior for true updates.
- Around line 211-223: assetParameters() is serializing created_at/updated_at
without timezone (using self::DATETIME_FORMAT), while the deserialization uses
DateTimeImmutable::createFromFormat() with no timezone, causing incorrect
round-trips across system timezones; update the serialization to produce
UTC-aware timestamps (either change self::DATETIME_FORMAT to an ISO-8601/offset
format like DateTime::ATOM or explicitly call ->setTimezone(new
DateTimeZone('UTC')) before formatting in assetParameters) and update the
corresponding parsing code that uses
DateTimeImmutable::createFromFormat()/DateTimeImmutable(...) to pass new
DateTimeZone('UTC') (or parse ISO-8601 directly) so both storage and
reconstruction consistently use UTC for created_at and updated_at.

In `@tests/Integration/Infrastructure/Persistence/AssetsTableBootstrapTest.php`:
- Around line 67-230: Tests contain inline boundary/equivalence matrices and
non-conforming names; extract those matrices into static provider methods and
annotate the tests with #[DataProvider('providerName')] and rename the test
methods to follow the itReturns…When… / itThrows…When… convention (e.g., change
itAcceptsRowsForEveryValidAssetLifecycleState to
itReturnsPersistedLifecycleStateWhenRowIsValid and move $validRows into a static
provider like provideValidLifecycleRows(), and change
itRejectsRowsThatViolateAssetLifecycleConstraints to
itThrowsConstraintViolationWhenRowInvalid with a static provider
provideInvalidLifecycleCases()); update the tests to accept provider parameters
and reuse helper methods like insertAssetRow, assertInsertFails,
validPendingRow, validFailedRow, validUploadedRow, and ensure provider methods
are static and referenced by name in the #[DataProvider] attribute.

In `@tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php`:
- Around line 35-444: Rename the test methods that use verbs like itSaves…,
itUpdates…, itAccepts…, and itKeeps… to the repository's required naming
convention (itReturns{X}When{Condition} / itThrows{Exception}When{Condition}),
e.g. rename itSavesAndReadsAPendingAsset ->
itReturnsAssetWhenSavingAndReadingAPendingAsset, itSavesAndReadsAnUploadedAsset
-> itReturnsAssetWhenSavingAndReadingAnUploadedAsset,
itUpdatesAnExistingRowAfterAnAssetStateChange ->
itReturnsUpdatedRowWhenAssetStateChanges,
itAcceptsALegitimateStateChangeWhenUpdatedAtRemainsEqual ->
itReturnsAcceptedStateWhenUpdatedAtRemainsEqual, and
itKeepsASingleRowWhenSavingTheSameUnchangedAssetTwice ->
itReturnsSingleRowWhenSavingUnchangedAssetTwice; update the method names in the
test class (MySQLAssetRepositoryTest) and any references to them so they follow
the itReturns/itThrows…When… pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: bb2e41cb-cee8-46f7-b66c-e19d263e4f43

📥 Commits

Reviewing files that changed from the base of the PR and between a0f3b89 and a357fe5.

📒 Files selected for processing (21)
  • docs/adr/04-asset-domain-contracts.md
  • docs/api/01-agree-api-schema.md
  • docs/logs/03-data-local-storage-setup.md
  • migrations/20260401120000_create_assets_table.sql
  • mkdocs.yml
  • src/Domain/Asset/Asset.php
  • src/Domain/Asset/AssetRepositoryInterface.php
  • src/Domain/Asset/ClockInterface.php
  • src/Domain/Asset/Exception/StaleAssetWriteException.php
  • src/Domain/Asset/SystemClock.php
  • src/Domain/Asset/ValueObject/UploadId.php
  • src/Domain/Asset/ValueObject/UploadTarget.php
  • src/Infrastructure/Persistence/MySQLAssetRepository.php
  • src/Infrastructure/Storage/MockStorageAdapter.php
  • tests/Integration/Infrastructure/Persistence/AssetsTableBootstrapTest.php
  • tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php
  • tests/Integration/Support/CompareAndSwapRacePdo.php
  • tests/Integration/Support/CompareAndSwapRaceStatement.php
  • tests/Unit/Domain/Asset/AssetTest.php
  • tests/Unit/Domain/Asset/ValueObject/UploadTargetTest.php
  • tests/Unit/Infrastructure/Storage/MockStorageAdapterTest.php

Comment thread src/Infrastructure/Persistence/MySQLAssetRepository.php
Comment thread src/Infrastructure/Persistence/MySQLAssetRepository.php Outdated
Comment thread tests/Integration/Infrastructure/Persistence/AssetsTableBootstrapTest.php Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Infrastructure/Persistence/MySQLAssetRepository.php`:
- Around line 194-210: In updateMutableFields, do not immediately throw
StaleAssetWriteException when $statement->rowCount() === 0; instead re-read the
current row (e.g. via the repository's findById/fetch method or by selecting the
asset by id) and compare its mutable fields to the desired state produced by
mutableUpdateParameters($asset, $persistedAsset); if the re-read row already
matches the intended status/chunk_count/completion_proof/updated_at, treat the
update as successful (return normally), otherwise throw
StaleAssetWriteException(self::STALE_WRITE_MESSAGE). Ensure you reference
updateMutableFields, mutableUpdateParameters and StaleAssetWriteException when
making the change.

In `@tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php`:
- Around line 12-13: Rename the test method
itCreatesTheExpectedAssetsTableSchemaAndCanBeReappliedSafely to follow the
required pattern; change the method name (the #[Test] method) to a form like
itReturnsExpectedSchemaWhenReappliedSafely (or another
itReturns/itThrows/itEmits/itDoesNot variant that matches the assertion intent)
so the test name matches the itReturns…When… style enforced for tests/**/*.php.

In `@tests/Integration/Infrastructure/Persistence/BaseAssetsTableTest.php`:
- Line 11: Rename the abstract test helper class BaseAssetsTableTest to
BaseAssetsTableFixture to avoid PHPUnit discovering it as a test; update the
class declaration for the abstract helper and then update any inheritance
references where other test classes extend it—specifically change extends
BaseAssetsTableTest to extends BaseAssetsTableFixture in AssetsTableSchemaTest
and AssetsTableLifecycleTest so the abstract fixture is no longer treated as a
concrete test by PHPUnit.

In `@tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php`:
- Line 168: Several test methods still use disallowed names like
itRejectsALaterSaveWhenTheSameAssetIdentityChangesAnImmutableField and the other
itSearches… / itSurfaces… methods; rename each to the approved pattern
(itReturns{X}When{Condition}, itThrows{Exception}When{Condition},
itEmits{Event}When{Condition}, or itDoesNot{X}When{Condition}). Concretely,
rename itRejectsALaterSaveWhenTheSameAssetIdentityChangesAnImmutableField to an
itThrows{Exception}WhenSameAssetIdentityChangesImmutableField form (choose the
actual Exception name used in the test), rename the itSearches… methods to
itReturns{ExpectedResult}When{Condition}, and rename the itSurfaces… methods to
itEmits{Event}When{Condition}; update the method declarations (e.g., the test
method names listed at lines 168, 212, 257, 337, 409) and any test references so
they match the new names.
- Around line 735-740: The tests are creating DateTimeImmutable instances
without specifying timezone, causing flaky failures on non-UTC CI; update
persistedState() to construct DateTimeImmutable objects using an explicit UTC
timezone (e.g., new \DateTimeZone('UTC')) for both createdAt and updatedAt, and
update assertAssetMatches() to normalize/compare timestamps in UTC (either by
creating expected DateTimeImmutable with UTC or converting actual values to UTC
before comparison) so assertions are timezone-independent; adjust any
parse/format logic in assertAssetMatches() to use UTC when comparing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 557006bd-61d5-458b-ab04-17ceefdc0343

📥 Commits

Reviewing files that changed from the base of the PR and between a357fe5 and 5ab1c9e.

📒 Files selected for processing (5)
  • src/Infrastructure/Persistence/MySQLAssetRepository.php
  • tests/Integration/Infrastructure/Persistence/AssetsTableLifecycleTest.php
  • tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php
  • tests/Integration/Infrastructure/Persistence/BaseAssetsTableTest.php
  • tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php

Comment thread src/Infrastructure/Persistence/MySQLAssetRepository.php
Comment thread tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php Outdated
Comment thread tests/Integration/Infrastructure/Persistence/BaseAssetsTableTest.php Outdated
Comment thread tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php Outdated
Comment thread tests/Integration/Infrastructure/Persistence/MySQLAssetRepositoryTest.php Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/Integration/Infrastructure/Persistence/AssetsTableLifecycleTest.php`:
- Around line 45-71: The test itThrowsConstraintViolationWhenRowInvalid
currently takes a boolean preInsertBaseRow to control inserting a base row;
extract that conditional pre-insert behaviour into a separate helper (e.g.,
create a new method like maybeInsertBaseRow(PDO $connection, ?array $baseRow,
bool $shouldInsert) or better yet split into two focused tests/useDataProvider
variants) and call it from inside the withTemporarySchema closure instead of
passing the flag; update usages of insertAssetRow, validPendingRow,
validFailedRow, validUploadedRow and the DataProvider cases to remove the
boolean parameter so the method signature for
itThrowsConstraintViolationWhenRowInvalid no longer needs the preInsertBaseRow
flag.

In `@tests/Integration/Infrastructure/Persistence/BaseAssetsTableTestCase.php`:
- Around line 141-152: The createConnection method returns a PDO without any
timeout—update createConnection to set a connection timeout so tests don't hang:
when constructing PDO in createConnection(string $dsn, string $user, string
$password) add a timeout option (e.g. include PDO::ATTR_TIMEOUT => <seconds> in
the options array, or if using a driver that requires it, append the appropriate
timeout parameter to the $dsn) and choose a reasonable short value (e.g. 5
seconds) so database connect attempts fail fast during tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6d7cbea3-5ce7-4afa-b51c-a4f0aa1bd6b3

📥 Commits

Reviewing files that changed from the base of the PR and between 5ab1c9e and 7c1d4b2.

📒 Files selected for processing (3)
  • tests/Integration/Infrastructure/Persistence/AssetsTableLifecycleTest.php
  • tests/Integration/Infrastructure/Persistence/AssetsTableSchemaTest.php
  • tests/Integration/Infrastructure/Persistence/BaseAssetsTableTestCase.php

@hsc00 hsc00 merged commit bbb6208 into main Apr 2, 2026
8 checks passed
@hsc00 hsc00 deleted the feat/data-and-local-storage-setup branch April 2, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

US-03: Data & Local Storage Setup

1 participant