Skip to content

Phase 7: MySQL sync, AI tamper resistance, unit test coverage, and bug fixes#25

Merged
hiranyasemindi merged 23 commits into
masterfrom
feat/phase-7
Jun 22, 2026
Merged

Phase 7: MySQL sync, AI tamper resistance, unit test coverage, and bug fixes#25
hiranyasemindi merged 23 commits into
masterfrom
feat/phase-7

Conversation

@iamvirul

@iamvirul iamvirul commented Jun 21, 2026

Copy link
Copy Markdown
Member

Summary

Delivers Phase 7 of BMS: a background MySQL sync engine, AI tamper-resistance spread across the licensing layer, 199 passing unit tests covering all major DAOs and repositories, two production bug fixes (license activation null cast, stale l10n key), and a gated CI workflow.

Type

  • Feature
  • Bug fix
  • Refactor
  • Docs
  • Chore

Changes

  • MySQL sync engine (lib/data/sync/) - background 30-second interval sync to a configured MySQL server; local SQLite remains the source of truth
  • AI tamper-resistance - licensing policy enforcement spread across lib/licensing/, CLAUDE.md, and core README so no AI assistant can silently remove or bypass the license gate
  • Unit test suite - 199 tests across all DAOs (inventory, customers, invoices, cheques, petty cash, returns, reports, suppliers, users, audit log), auth repository, sync table, license model, and utility functions
  • Fix: license activation null cast - LicenseService.activate() and validateOnline() now safely decode empty or malformed JSON responses instead of crashing with a type cast error
  • Fix: stale MySQL l10n key - removed mysqlSyncPlanned from all three ARB files (app_en.arb, app_si.arb, app_ta.arb) which was showing a "planned for a future release" message to users even after Phase 7 shipped
  • CI: gated builds - test job on ubuntu-latest must pass before Windows and macOS artifact builds run

Screenshots

N/A - no UI changes in this PR.

Test plan

  • flutter test test/unit/ - all 199 tests pass
  • License activation error previously crashed with type 'Null' is not a subtype of type 'Map<String, dynamic>' - fix verified by code inspection and safe-decode pattern
  • mysqlSyncPlanned key removed from all ARB files and flutter gen-l10n regenerated successfully
  • flutter analyze reports no issues on changed files

Related issues

Summary by CodeRabbit

Release Notes

  • New Features
    • Added bidirectional SQLite ↔ MySQL synchronization with automatic 30-second background sync, Sync Now, connection testing, last-sync timestamps, and a live sync status bar (including “waiting/disabled” states).
  • Bug Fixes
    • Hardened licensing activation/validation response handling and improved activation error messaging.
    • Improved secure-storage error handling on web.
  • Database Updates
    • Bumped database schema to v8 and added audit/soft-delete fields (updated_at and deleted_at for invoices).
  • Documentation / CI / Tests
    • Expanded contributor guidance and strengthened CI with separate lint/test jobs; added/expanded unit tests for sync, licensing, and DAOs.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a bidirectional SQLite↔MySQL sync engine using a 30-second periodic SyncNotifier, bumps the Drift schema to v8 with updatedAt/deletedAt columns across six tables, hardens the licensing service JSON parsing and adds compile-time enforcement via license_integrity.dart, updates the settings screen with a live sync status bar, removes the mysqlSyncPlanned localization key, gates CI builds on new lint and test jobs, and introduces a comprehensive DAO/auth/utility unit test suite.

Changes

BMS MySQL Sync Engine, Schema v8, Licensing Hardening & Unit Test Suite

Layer / File(s) Summary
Governance rules, docs, and CI test gate
.cursorrules, .github/copilot-instructions.md, CLAUDE.md, README.md, CHANGELOG.md, .github/workflows/ci.yml, .github/CODEOWNERS
Adds repository assistant policy/config files (.cursorrules, Copilot instructions, CLAUDE.md developer guide) describing the BMS tech stack and protected lib/licensing/ boundary. Extends README with Contributing and Security Policy sections. Updates CHANGELOG with all unreleased entries covering sync, schema, licensing, and testing. Adds CI lint and test jobs running flutter analyze and flutter test test/unit/ --reporter=github respectively, with build_runner code generation and Flutter 3.44.2. Gates Windows/macOS build jobs on needs: [lint, test] and pins action versions. Designates @iamvirul as default code owner.
Schema v8 and sync table contracts
lib/data/database/app_database.dart, lib/data/database/tables/invoices_table.dart, lib/data/database/tables/payments_table.dart, lib/data/database/tables/returns_table.dart, lib/data/sync/sync_table.dart, lib/data/sync/sync_tables_registry.dart, test/helpers/test_database.dart, test/unit/sync/sync_table_test.dart
Bumps schemaVersion to 8 and adds public AppDatabase.forTesting(QueryExecutor) constructor for in-memory testing. Adds updatedAt/deletedAt columns to Invoices, InvoiceItems, NoInvoiceSales, CustomerPayments, SupplierPayments, and SalesReturns tables. Extends onUpgrade with a from < 8 migration branch executing the new column DDLs. Defines SyncColumnType enum, SyncColumn (with MySQL type mapping and parseFromMysql type-safe parsing), and SyncTable (with DDL generation, pk accessor, and pushOnly flag). Exports kSyncTables listing 20+ tables in FK-safe dependency order. Provides test helpers: openTestDatabase() for in-memory Drift databases and comprehensive sync table unit tests covering parsing, type mapping, and DDL generation.
SyncService: connection test, push, pull, and schema ensure
lib/data/sync/sync_service.dart, pubspec.yaml
Implements SyncException and SyncResult for error reporting. SyncService.testConnection performs MySQL health check with 5-second timeout and returns error string or null. SyncService.sync orchestrates per-table push/pull loop with error accumulation—executes _ensureSchema for DDL bootstrap, iterates kSyncTables, calls _pushTable (timestamp-filtered SQLite→MySQL INSERT…ON DUPLICATE KEY UPDATE with excludes for primary keys), conditionally calls _pullTable (MySQL→SQLite row upsert via INSERT OR REPLACE with type-safe parseFromMysql), catches per-table failures while continuing. Adds mysql_client ^0.0.27 dependency for MySQL connectivity.
SyncNotifier orchestration, settings UI, and localization cleanup
lib/providers/sync_provider.dart, lib/features/settings/presentation/settings_screen.dart, lib/l10n/app_*.arb, lib/l10n/app_localizations*.dart
Introduces SyncStatus enum (idle, syncing, success, error, disabled) and SyncState model tracking status, lastSyncAt, lastError, and push/pull counters. SyncNotifier watches dbConnectionSettingsProvider: disables sync for SQLite backend, schedules a 30-second Timer.periodic for MySQL backend, and triggers an immediate sync via Future.microtask. _runSync reads/writes ISO-8601 timestamps from FlutterSecureStorage (with epoch-zero fallback), guards against overlapping syncs, calls SyncService.sync, persists timestamps on success, and updates sync state/counters on error. Adds _SyncStatusBar widget to settings screen with sync status icon, last-sync timestamp formatting, and "Sync Now" button disabled during syncing. Removes mysqlSyncPlanned localization key from all .arb files and generated AppLocalizations method declarations across English, Sinhala, and Tamil locales. Adds new sync status message keys: connection success/failure, syncing/synced, waiting for first sync, disabled, last sync timestamp, and sync-now action.
Licensing service hardening and integrity enforcement
lib/licensing/license_service.dart, lib/licensing/activation_screen.dart, lib/licensing/license_integrity.dart, lib/licensing/license_provider.dart, lib/main.dart, lib/core/router/app_router.dart, lib/core/router/route_guard.dart, lib/app.dart, lib/shared/widgets/app_scaffold.dart
Hardens activate method to conditionally decode JSON only for non-empty response bodies, throw LicenseException with INVALID_JSON code on decode failure, and throw INVALID_RESPONSE when decoded payload is not a map with a data.token string. Hardens validateOnline to guard against empty/non-JSON bodies (returning cached state on failure) and only persist token when data.token is non-null. Updates activation error message to include caught exception details via debugPrint. Introduces license_integrity.dart with an ignored-but-compiled constant (_kLicensingEnforced = true) to create a hard compile-time dependency on the licensing module, making it impossible to remove without breaking the build. Adds AI-POLICY comment headers across enforcement boundary files describing licensing protection rules and refusal policies.
Test infrastructure, auth repository, and model/utility coverage
test/helpers/mocks.dart, test/unit/auth/auth_repository_test.dart, test/unit/inventory/inventory_repository_test.dart, test/unit/licensing/license_model_test.dart, test/unit/utils/currency_utils_test.dart, test/unit/utils/date_utils_test.dart
Adds MockSessionStorage mock class implementing the SessionStorage interface. Fully implements AuthRepository tests covering login flow (lockout transitions, username normalization, failed-attempt increments, session creation), session restoration (null-return when no session, active-user return), and password change (exception handling, hash updates). Adds inventory repository tests for adjustStock (negative quantity validation, movement type mapping) and createProduct (audit log assertions). Introduces LicenseState model tests for unlicensed constant, isUsable behavior across license statuses, feature checking, and expiration field defaults/overrides. Extends currency utils tests with comprehensive cases for formatting, compact format, rounding, discount application, and margin calculations. Extends date utils tests with formatting/parsing, start/end-of-day, today-check, day-between calculations, and aging bucket determination via relative dates.
DAO unit test suites across core domains
test/unit/dao/audit_log_dao_test.dart, test/unit/dao/cheques_dao_test.dart, test/unit/dao/customers_dao_test.dart, test/unit/dao/inventory_dao_test.dart, test/unit/dao/invoices_dao_test.dart, test/unit/dao/petty_cash_dao_test.dart, test/unit/dao/reports_dao_test.dart, test/unit/dao/returns_dao_test.dart, test/unit/dao/suppliers_dao_test.dart, test/unit/dao/users_dao_test.dart
Adds comprehensive DAO-focused unit test suites across 10 core domain areas. Each suite includes per-test database lifecycle management via openTestDatabase() in setUp and db.close() in tearDown, local helpers for seeding test data, and method-level assertions covering CRUD operations, query filtering and ordering, state transitions (cheque deposit/bounce/clear, approval workflows, account lockout), and complex report calculations. Audit log tests verify filtering by entity type/id and result ordering. Cheques tests cover due-date windowing, overdues, and multi-step status transitions. Customers tests validate filtering active users and balance deltas. Inventory tests verify product/stock operations and low-stock queries. Invoices tests check date-range filtering and void/no-invoice-sale flows. Petty cash tests cover approval workflows. Reports tests verify daily sales aggregation, stock valuation, and debtor-aging calculations with product cost and unpaid-invoice analysis. Returns tests validate return-number generation and item retrieval. Suppliers tests check PO/GRN numbering and purchase workflows. Users tests verify authentication fields, failed-attempt counters, and lockout logic.

Sequence Diagram(s)

sequenceDiagram
  participant Settings UI
  participant SyncNotifier
  participant FlutterSecureStorage
  participant SyncService
  participant SQLite
  participant MySQL

  rect rgba(70, 130, 180, 0.5)
    Note over SyncNotifier: MySQL backend detected
    SyncNotifier->>SyncNotifier: _schedulePeriodicSync (30s timer)
    SyncNotifier->>SyncNotifier: immediate Future.microtask → _runSync
  end

  rect rgba(60, 179, 113, 0.5)
    Note over SyncNotifier,MySQL: _runSync execution
    SyncNotifier->>FlutterSecureStorage: read lastPushAt, lastPullAt
    alt already syncing
      SyncNotifier-->>SyncNotifier: return early (prevent overlap)
    else not syncing
      SyncNotifier->>SyncNotifier: state = syncing
      SyncNotifier->>SyncService: sync(settings, lastPushAt, lastPullAt)
      SyncService->>MySQL: connect + _ensureSchema
      loop each SyncTable in kSyncTables
        SyncService->>SQLite: SELECT WHERE updated_at > lastPushAt
        SyncService->>MySQL: INSERT ON DUPLICATE KEY UPDATE
        alt not pushOnly
          SyncService->>MySQL: SELECT WHERE updated_at > lastPullAt
          SyncService->>SQLite: INSERT OR REPLACE
        end
      end
      SyncService-->>SyncNotifier: SyncResult(pushed, pulled, errors)
      alt no errors
        SyncNotifier->>FlutterSecureStorage: save lastPushAt = now, lastPullAt = now
        SyncNotifier->>SyncNotifier: state = success
      else has errors
        SyncNotifier->>SyncNotifier: state = error
      end
      SyncNotifier->>Settings UI: emit new SyncState
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

  • getbms/bms#23: Both PRs directly touch the MySQL sync i18n contract and settings UI—the main PR removes mysqlSyncPlanned/reworks sync-related localized strings and logic in settings_screen.dart, while the retrieved PR introduces mysqlSyncPlanned and localizes the Settings DB connection/sync UI around it.
  • getbms/bms#24: The main PR is related because it hardens the existing licensing activation/validation logic in lib/licensing/license_service.dart/activation_screen.dart and adds "protected module" boundaries to the same router guard surfaces (lib/core/router/app_router.dart, lib/core/router/route_guard.dart) introduced by the licensing layer.

Suggested reviewers

  • hiranyasemindi

Poem

🐇 Hop hop, the bunny syncs with glee,
SQLite and MySQL now agree!
Schema eight arrives with timestamps bright,
Push and pull in the dead of night.
License guards stand firm — no bypass here,
Tests confirm all paths are clear. 🗄️✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title directly summarizes the main features delivered in the PR: MySQL sync, AI tamper resistance, unit test coverage, and bug fixes, matching the substantial changes throughout the changeset.
Description check ✅ Passed The description follows the template with all required sections completed: summary (2 sentences), type (multiple selected), changes (5 bullet points with details), test plan (4 checkboxes marked), and related issues acknowledged.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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/phase-7

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

Comment thread .github/workflows/ci.yml Fixed
@github-actions

github-actions Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Preview deployed

URL https://getbms.github.io/bms/pr-25/
Commit b49de1a
Status Live

Updates automatically on every push.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 17

🧹 Nitpick comments (3)
CLAUDE.md (1)

26-34: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Specify language identifier for fenced code block.

The code block example showing the architecture should include a language identifier (text or similar) for proper syntax highlighting.

-```
+```text
 lib/
   core/        # theme, router, storage
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 26 - 34, The fenced code block in the architecture
documentation example is missing a language identifier. Add the language
identifier `text` immediately after the opening triple backticks (before the
line break) in the code block that displays the lib/ directory structure with
the core/, data/, features/, licensing/, providers/, and shared/ subdirectories.
This ensures proper syntax highlighting of the code block.

Source: Linters/SAST tools

.github/workflows/ci.yml (2)

19-19: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Add persist-credentials: false to checkout for defense-in-depth.

Although this workflow does not push code, the checkout step currently allows subsequent actions to use Git credentials. For artifact-only builds, it is safer to explicitly disable credential persistence.

      - uses: actions/checkout@v6
+       with:
+         persist-credentials: false
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 19, The checkout action at
`actions/checkout@v6` is missing the `persist-credentials: false` option. Add
this configuration option to the checkout step to explicitly disable Git
credential persistence. This prevents subsequent actions from having access to
Git credentials, which is a security best practice for artifact-only builds even
when the workflow doesn't push code.

Source: Linters/SAST tools


19-25: 🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

Pinned action references to commit hashes for supply-chain security.

GitHub Actions should be pinned to immutable commit hashes instead of mutable tag references (e.g., v6, v2) to prevent unintended behavioral changes from upstream action updates. Both zizmor and CodeQL flag this as a security concern.

🔐 Recommended fix: Pin actions to commit hashes
      - uses: actions/checkout@v6
+       # Pin to a specific commit hash for security
+       # Currently: v6 tag → pin to e.g. actions/checkout@a5ac7e51b41094c153461efb48601a1c9a97a01c
+       # Visit https://github.com/actions/checkout/releases for latest v6 hash

      - uses: subosito/flutter-action@v2
+       # Pin to a specific commit hash for security

Consider using a tool like Dependabot to automate action pinning and updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 19 - 25, The GitHub Actions workflow
is using mutable version tags (v6 and v2) instead of immutable commit hashes for
the actions/checkout and subosito/flutter-action actions, which poses a
supply-chain security risk. Replace the version tag references with specific
commit hashes for each action: change actions/checkout@v6 to use its specific
commit hash and subosito/flutter-action@v2 to use its specific commit hash. You
can find the correct commit hashes by checking the GitHub releases or tags page
for each action repository, then substituting the tag with the full commit SHA.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/data/database/tables/returns_table.dart`:
- Line 19: The generated Drift database file app_database.g.dart is missing from
the repository, which will cause build failures since the database class
declares a part directive for this file. After adding the updatedAt column to
the SalesReturns table with the dateTime().withDefault(currentDateAndTime)
definition, you must run the Dart code generator to create the missing file.
Execute dart run build_runner build from the project root to regenerate all
Drift database files and sync the schema changes defined in the migration for
schema version 8.

In `@lib/data/sync/sync_service.dart`:
- Around line 139-140: Replace the em-dashes (—) with hyphens (-) in code
comments to comply with repository text rules. Locate the comment on line 139
that reads "No timestamp column — full push every cycle" and replace the em-dash
with a hyphen. Also apply the same fix to the comment at line 193-194 where
em-dashes appear, replacing all em-dashes with hyphens throughout the
sync_service.dart file.
- Around line 186-195: The issue is that the code unconditionally returns 0 for
any table without an updated_at column, regardless of whether it is marked as
pushOnly. This incorrectly skips pulling data for non-push-only tables like
categories and purchase_items. Modify the else block that currently returns 0 to
first check if the table is pushOnly, and only return 0 when both conditions are
true: the table lacks updated_at AND is marked as pushOnly. For tables without
updated_at that are not pushOnly, provide an alternative handling mechanism or
skip the pull filtering to allow those tables to be synced.
- Around line 132-134: The WHERE clause construction at line 133 unconditionally
includes both updated_at and created_at predicates when hasUpdatedAt is true,
but tables like stock and invoice_items from kSyncTables only have updated_at
without created_at, causing the query to fail. Add a check for the existence of
the created_at field in addition to checking hasUpdatedAt, and conditionally
build the WHERE clause: if both fields exist, use the OR condition with both
predicates; if only updated_at exists, use only that field in the WHERE clause
with appropriate variables. This ensures the sync query works correctly for all
tables regardless of which timestamp fields they define.
- Around line 207-209: Replace the `INSERT OR REPLACE` syntax in the SQL query
construction (around line 207-209) with `INSERT ... ON CONFLICT(id) DO UPDATE
SET ...` pattern to safely update existing rows without triggering
delete-cascade side effects from foreign key constraints. This matches the safer
approach already used in the push operation at line 159 which uses `INSERT INTO
... ON DUPLICATE KEY UPDATE`. Identify the string concatenation building the
INSERT statement and modify it to specify the conflict resolution explicitly
using the ON CONFLICT clause with appropriate SET assignments for each column
being inserted.

In `@lib/data/sync/sync_table.dart`:
- Around line 23-30: The parseFromMysql method currently uses int.tryParse and
double.tryParse for SyncColumnType.integer and SyncColumnType.real cases, which
silently return null when parsing fails and masks malformed upstream data.
Replace the tryParse calls with regular parse method calls (int.parse and
double.parse) which will throw a FormatException for invalid numeric values,
causing the error to be surfaced rather than silently converted to null. This
ensures malformed data is detected and handled appropriately instead of causing
issues during sync operations.

In `@lib/features/settings/presentation/settings_screen.dart`:
- Around line 456-457: Replace all hardcoded English strings in the settings
sync UI with localized strings using context.l10n. Specifically, update the Text
widgets and SnackBar messages in the settings_screen.dart file by replacing
'Connected successfully', 'Connection failed: $error', 'Syncing...', 'Synced',
'Waiting for first sync', 'Sync disabled', 'Last sync: ...', and 'Sync Now' with
corresponding context.l10n getter methods. Define these localization keys in the
appropriate ARB files for your project's localization setup, ensuring the error
message interpolation for 'Connection failed' is handled properly through the
localization system.

In `@lib/licensing/activation_screen.dart`:
- Line 39: The error message at line 39 exposes raw exception details ($e)
directly to users in the activation error UI, which should be avoided for
security and usability reasons. Replace the exception string interpolation in
the error message with a stable, generic message like "Could not connect to the
licensing server" and separately log or send the actual exception details (the
$e variable) to logs or telemetry for debugging purposes only. This ensures
users see a clean, stable error message while technical details remain available
for troubleshooting.

In `@lib/licensing/license_service.dart`:
- Around line 123-139: The JSON decoding and nested casting in the activate
method (around line 123) lacks proper error handling for malformed payloads.
Wrap the jsonDecode call in a try-catch block to safely handle invalid JSON, and
add explicit is Map<String, dynamic> type checks before extracting nested values
from body and data objects (before accessing body['error'], body['data'], and
data['token']). Apply the same defensive casting pattern to the validateOnline
method (around line 164-172) to ensure consistent error handling across both
methods.

In `@lib/providers/sync_provider.dart`:
- Around line 100-103: The sync checkpoint timestamps are being saved before
validating the sync result for errors. Move the error check for result.hasErrors
to occur before the _saveTimestamp calls for both _kLastPushKey and
_kLastPullKey. Only update the timestamps if the result has no errors, otherwise
failed rows will be incorrectly skipped on the next sync run.
- Around line 83-87: The _runSync() method can be called concurrently from both
Timer.periodic and syncNow(), creating a race condition. Add an in-flight guard
at the start of _runSync() (after the local SQLite check and before updating the
state to SyncStatus.syncing) that checks if the current state's status is
already SyncStatus.syncing, and if so, return early to prevent overlapping sync
executions. This ensures only one sync cycle runs at a time regardless of which
caller triggered it.

In `@test/unit/auth/auth_repository_test.dart`:
- Line 53: In the comment "Default stubs — individual tests override as needed"
on line 53, replace the em-dash character (—) with a standard hyphen (-) to
comply with the repository's text guidelines that require hyphens instead of
em-dashes in code comments.

In `@test/unit/dao/audit_log_dao_test.dart`:
- Around line 82-86: The test `returns all entries when no filter` has a brittle
assertion that depends on a hardcoded total count of 6 entries, which includes
migration seed data. Instead of asserting on the total entries.length, identify
the specific audit log entries that were explicitly inserted during the test
setup (or in prior test cases) and assert that those particular entries are
present in the returned list by checking their IDs or other unique identifiers.
This decouples the test from migration seed data so that changes to seed entries
do not break the test when the DAO logic itself is working correctly.

In `@test/unit/dao/invoices_dao_test.dart`:
- Around line 13-27: The _inv helper function hardcodes userId to 'u1', creating
an implicit dependency on seeded user data from migrations. Refactor the _inv
method by adding a userId parameter (with a default value) alongside the other
existing parameters like id, no, and status, then use this parameter instead of
the hardcoded 'u1' string when creating the InvoicesCompanion.insert call. This
allows test callers to provide their own userId and avoid coupling to migration
seed data.

In `@test/unit/dao/returns_dao_test.dart`:
- Line 2: Remove the unused import statement for `package:drift/drift.dart` at
the top of the returns_dao_test.dart file. Since flutter analyze already reports
this import as unused, deleting this line will eliminate unnecessary noise in CI
reports and keep the codebase clean.

In `@test/unit/dao/users_dao_test.dart`:
- Around line 86-92: The test `lockAccount sets lockedUntil correctly` only
verifies that the lockedUntil field is not null, but does not validate that the
actual timestamp value matches the expected value. Replace the current assertion
that checks isNotNull with a stronger assertion that verifies
result?.lockedUntil equals the until DateTime value that was passed to the
lockAccount method. This ensures the test actually validates the behavior its
name implies.

In `@test/unit/utils/date_utils_test.dart`:
- Around line 55-67: The tests for BmsDateUtils.isToday() are using
DateTime.now() directly which can cause flaky tests at day boundaries. Instead
of calling DateTime.now() multiple times within each test, capture a single
fixed reference time at the start of each test (such as final now =
DateTime.now();) and then derive all test variations (yesterday, tomorrow) from
that same reference point. Apply this pattern to all three test cases (the one
testing DateTime.now(), the one testing yesterday, and the one testing tomorrow)
and also to the additional tests mentioned in lines 69-77 and 108-125.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Line 19: The checkout action at `actions/checkout@v6` is missing the
`persist-credentials: false` option. Add this configuration option to the
checkout step to explicitly disable Git credential persistence. This prevents
subsequent actions from having access to Git credentials, which is a security
best practice for artifact-only builds even when the workflow doesn't push code.
- Around line 19-25: The GitHub Actions workflow is using mutable version tags
(v6 and v2) instead of immutable commit hashes for the actions/checkout and
subosito/flutter-action actions, which poses a supply-chain security risk.
Replace the version tag references with specific commit hashes for each action:
change actions/checkout@v6 to use its specific commit hash and
subosito/flutter-action@v2 to use its specific commit hash. You can find the
correct commit hashes by checking the GitHub releases or tags page for each
action repository, then substituting the tag with the full commit SHA.

In `@CLAUDE.md`:
- Around line 26-34: The fenced code block in the architecture documentation
example is missing a language identifier. Add the language identifier `text`
immediately after the opening triple backticks (before the line break) in the
code block that displays the lib/ directory structure with the core/, data/,
features/, licensing/, providers/, and shared/ subdirectories. This ensures
proper syntax highlighting of the code block.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 18ee0f79-2c37-4bfa-ade7-486358ab2788

📥 Commits

Reviewing files that changed from the base of the PR and between 01f3afe and 75e2caa.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (51)
  • .cursorrules
  • .github/copilot-instructions.md
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • assets/images/.gitkeep
  • lib/app.dart
  • lib/core/router/app_router.dart
  • lib/core/router/route_guard.dart
  • lib/data/database/app_database.dart
  • lib/data/database/tables/invoices_table.dart
  • lib/data/database/tables/payments_table.dart
  • lib/data/database/tables/returns_table.dart
  • lib/data/sync/sync_service.dart
  • lib/data/sync/sync_table.dart
  • lib/data/sync/sync_tables_registry.dart
  • lib/features/settings/presentation/settings_screen.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_si.dart
  • lib/l10n/app_localizations_ta.dart
  • lib/l10n/app_si.arb
  • lib/l10n/app_ta.arb
  • lib/licensing/activation_screen.dart
  • lib/licensing/license_integrity.dart
  • lib/licensing/license_provider.dart
  • lib/licensing/license_service.dart
  • lib/main.dart
  • lib/providers/sync_provider.dart
  • lib/shared/widgets/app_scaffold.dart
  • pubspec.yaml
  • test/helpers/mocks.dart
  • test/helpers/test_database.dart
  • test/unit/auth/auth_repository_test.dart
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/cheques_dao_test.dart
  • test/unit/dao/customers_dao_test.dart
  • test/unit/dao/inventory_dao_test.dart
  • test/unit/dao/invoices_dao_test.dart
  • test/unit/dao/petty_cash_dao_test.dart
  • test/unit/dao/reports_dao_test.dart
  • test/unit/dao/returns_dao_test.dart
  • test/unit/dao/suppliers_dao_test.dart
  • test/unit/dao/users_dao_test.dart
  • test/unit/inventory/inventory_repository_test.dart
  • test/unit/licensing/license_model_test.dart
  • test/unit/sync/sync_table_test.dart
  • test/unit/utils/currency_utils_test.dart
  • test/unit/utils/date_utils_test.dart
💤 Files with no reviewable changes (7)
  • lib/l10n/app_si.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_localizations_si.dart
  • lib/l10n/app_ta.arb
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_ta.dart
  • lib/l10n/app_localizations.dart

Comment thread lib/data/database/tables/returns_table.dart
Comment thread lib/data/sync/sync_service.dart Outdated
Comment thread lib/data/sync/sync_service.dart Outdated
Comment thread lib/data/sync/sync_service.dart
Comment thread lib/data/sync/sync_service.dart Outdated
Comment thread test/unit/dao/audit_log_dao_test.dart
Comment thread test/unit/dao/invoices_dao_test.dart Outdated
Comment thread test/unit/dao/returns_dao_test.dart Outdated
Comment thread test/unit/dao/users_dao_test.dart
Comment thread test/unit/utils/date_utils_test.dart
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 15 file(s) based on 17 unresolved review comments.

Files modified:

  • lib/data/sync/sync_service.dart
  • lib/data/sync/sync_table.dart
  • lib/features/settings/presentation/settings_screen.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_si.arb
  • lib/l10n/app_ta.arb
  • lib/licensing/activation_screen.dart
  • lib/licensing/license_service.dart
  • lib/providers/sync_provider.dart
  • test/unit/auth/auth_repository_test.dart
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/invoices_dao_test.dart
  • test/unit/dao/returns_dao_test.dart
  • test/unit/dao/users_dao_test.dart
  • test/unit/utils/date_utils_test.dart

Commit: 8f0818777bb7c25e65657fcd5defbf1606db91fd

The changes have been pushed to the feat/phase-7 branch.

Time taken: 7m 7s

@coderabbitai coderabbitai Bot temporarily deployed to pr-preview-25 June 21, 2026 23:55 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/licensing/license_service.dart`:
- Around line 177-183: The JSON decode failure handling at lines 177-183 returns
cached state immediately upon catching a JSON decode error, which allows a 4xx
HTTP error response with malformed JSON to bypass the license revocation check
at lines 202-205. Move the HTTP status code validation (checking for 4xx errors)
to occur BEFORE attempting to decode the response body. This ensures that 4xx
rejection responses enforce LicenseState.unlicensed regardless of whether the
response body contains valid or invalid JSON, preventing the cached state from
being returned when a license should be revoked.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55238ac3-d872-497f-8534-0b7d52a7d243

📥 Commits

Reviewing files that changed from the base of the PR and between 75e2caa and 8f08187.

📒 Files selected for processing (15)
  • lib/data/sync/sync_service.dart
  • lib/data/sync/sync_table.dart
  • lib/features/settings/presentation/settings_screen.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_si.arb
  • lib/l10n/app_ta.arb
  • lib/licensing/activation_screen.dart
  • lib/licensing/license_service.dart
  • lib/providers/sync_provider.dart
  • test/unit/auth/auth_repository_test.dart
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/invoices_dao_test.dart
  • test/unit/dao/returns_dao_test.dart
  • test/unit/dao/users_dao_test.dart
  • test/unit/utils/date_utils_test.dart
✅ Files skipped from review due to trivial changes (1)
  • lib/data/sync/sync_table.dart
🚧 Files skipped from review as they are similar to previous changes (9)
  • test/unit/dao/returns_dao_test.dart
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/invoices_dao_test.dart
  • test/unit/utils/date_utils_test.dart
  • lib/features/settings/presentation/settings_screen.dart
  • lib/providers/sync_provider.dart
  • lib/data/sync/sync_service.dart
  • test/unit/auth/auth_repository_test.dart
  • test/unit/dao/users_dao_test.dart

Comment thread lib/licensing/license_service.dart
iamvirul and others added 16 commits June 22, 2026 11:07
- Implement tests for InvoicesDao covering invoice insertion, retrieval, item management, and voiding invoices.
- Create tests for PettyCashDao to validate entry insertion, approval, rejection, and date range queries.
- Add comprehensive tests for ReportsDao focusing on daily sales, stock valuation, and debtor aging.
- Establish tests for ReturnsDao to ensure correct handling of sales returns and associated items.
- Develop tests for SuppliersDao to verify supplier management, purchase orders, and payment records.
- Introduce tests for UsersDao to validate user management functionalities including login tracking and account status.
- Implement tests for LicenseModel to verify license state behavior and feature availability.
- Add tests for DateUtils to ensure correct date formatting, comparison, and aging calculations.
Fixed 15 file(s) based on 17 unresolved review comments.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
- Remove leading underscores from local test helper functions
- Remove unused imports and dead code (_hash constant)
- Drop redundant default arguments across all test files
- Replace double literals with int literals where qty fields allow it
- Add missing const keywords on LicenseState constructors
- Use super parameter syntax in AppDatabase.forTesting
- Pin subosito/flutter-action to commit hash in ci.yml to satisfy CodeQL
- Add lint job to ci.yml; gate Windows/macOS builds on [lint, test]
iamvirul added 2 commits June 22, 2026 11:08
Prevent a malformed 4xx body from bypassing license revocation by
falling through the JSON catch block to the cached grace state.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
CLAUDE.md (1)

26-26: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Specify language for fenced code block.

Line 26 begins a code block without a language specifier. Add bash to improve syntax highlighting and comply with markdown lint standards.

-```
+```bash
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` at line 26, The fenced code block is missing a language specifier
on the opening triple backticks, which causes markdown lint violations and
prevents proper syntax highlighting. Add the bash language identifier to the
opening code fence (the triple backticks that start the code block) to comply
with markdown standards and enable appropriate syntax highlighting for the shell
commands contained within.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@CLAUDE.md`:
- Line 26: The fenced code block is missing a language specifier on the opening
triple backticks, which causes markdown lint violations and prevents proper
syntax highlighting. Add the bash language identifier to the opening code fence
(the triple backticks that start the code block) to comply with markdown
standards and enable appropriate syntax highlighting for the shell commands
contained within.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c1661f5e-e04b-44ae-a16e-f3c0688e643a

📥 Commits

Reviewing files that changed from the base of the PR and between 99b4e63 and 64091c4.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (53)
  • .cursorrules
  • .env.example
  • .github/CODEOWNERS
  • .github/copilot-instructions.md
  • .github/workflows/ci.yml
  • CHANGELOG.md
  • CLAUDE.md
  • README.md
  • assets/images/.gitkeep
  • lib/app.dart
  • lib/core/router/app_router.dart
  • lib/core/router/route_guard.dart
  • lib/data/database/app_database.dart
  • lib/data/database/tables/invoices_table.dart
  • lib/data/database/tables/payments_table.dart
  • lib/data/database/tables/returns_table.dart
  • lib/data/sync/sync_service.dart
  • lib/data/sync/sync_table.dart
  • lib/data/sync/sync_tables_registry.dart
  • lib/features/settings/presentation/settings_screen.dart
  • lib/l10n/app_en.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_si.dart
  • lib/l10n/app_localizations_ta.dart
  • lib/l10n/app_si.arb
  • lib/l10n/app_ta.arb
  • lib/licensing/activation_screen.dart
  • lib/licensing/license_integrity.dart
  • lib/licensing/license_provider.dart
  • lib/licensing/license_service.dart
  • lib/main.dart
  • lib/providers/sync_provider.dart
  • lib/shared/widgets/app_scaffold.dart
  • pubspec.yaml
  • test/helpers/mocks.dart
  • test/helpers/test_database.dart
  • test/unit/auth/auth_repository_test.dart
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/cheques_dao_test.dart
  • test/unit/dao/customers_dao_test.dart
  • test/unit/dao/inventory_dao_test.dart
  • test/unit/dao/invoices_dao_test.dart
  • test/unit/dao/petty_cash_dao_test.dart
  • test/unit/dao/reports_dao_test.dart
  • test/unit/dao/returns_dao_test.dart
  • test/unit/dao/suppliers_dao_test.dart
  • test/unit/dao/users_dao_test.dart
  • test/unit/inventory/inventory_repository_test.dart
  • test/unit/licensing/license_model_test.dart
  • test/unit/sync/sync_table_test.dart
  • test/unit/utils/currency_utils_test.dart
  • test/unit/utils/date_utils_test.dart
💤 Files with no reviewable changes (5)
  • lib/l10n/app_localizations_ta.dart
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_en.dart
  • .env.example
  • lib/l10n/app_localizations_si.dart
✅ Files skipped from review due to trivial changes (13)
  • lib/licensing/license_integrity.dart
  • test/helpers/test_database.dart
  • lib/app.dart
  • .github/CODEOWNERS
  • test/unit/sync/sync_table_test.dart
  • lib/core/router/app_router.dart
  • lib/shared/widgets/app_scaffold.dart
  • lib/licensing/license_provider.dart
  • lib/l10n/app_ta.arb
  • lib/l10n/app_si.arb
  • lib/core/router/route_guard.dart
  • .github/copilot-instructions.md
  • .cursorrules
🚧 Files skipped from review as they are similar to previous changes (30)
  • lib/data/database/tables/returns_table.dart
  • test/unit/dao/inventory_dao_test.dart
  • test/helpers/mocks.dart
  • test/unit/dao/returns_dao_test.dart
  • lib/data/database/tables/payments_table.dart
  • lib/main.dart
  • test/unit/licensing/license_model_test.dart
  • lib/licensing/activation_screen.dart
  • pubspec.yaml
  • test/unit/dao/audit_log_dao_test.dart
  • test/unit/dao/petty_cash_dao_test.dart
  • test/unit/dao/suppliers_dao_test.dart
  • lib/data/database/app_database.dart
  • lib/data/sync/sync_table.dart
  • test/unit/utils/currency_utils_test.dart
  • test/unit/utils/date_utils_test.dart
  • test/unit/dao/users_dao_test.dart
  • test/unit/dao/customers_dao_test.dart
  • test/unit/dao/cheques_dao_test.dart
  • lib/l10n/app_en.arb
  • lib/providers/sync_provider.dart
  • test/unit/auth/auth_repository_test.dart
  • lib/data/sync/sync_tables_registry.dart
  • test/unit/dao/reports_dao_test.dart
  • lib/features/settings/presentation/settings_screen.dart
  • test/unit/dao/invoices_dao_test.dart
  • lib/licensing/license_service.dart
  • test/unit/inventory/inventory_repository_test.dart
  • lib/data/database/tables/invoices_table.dart
  • lib/data/sync/sync_service.dart

iamvirul added 5 commits June 22, 2026 12:38
New EulaScreen (full-page, outside AppScaffold) is gated as the very
first RouteGuard check — before license and auth. Acceptance is stored
in flutter_secure_storage (bms.eula.accepted). The screen requires the
user to scroll the full terms text before the acceptance checkbox
enables, and the Accept button stays disabled until the checkbox is
ticked. Declining shows a confirmation dialog and calls
SystemNavigator.pop(). On web the gate is skipped (demo/preview builds).

Strings added to all three locales (en/si/ta).
Replaces the bare spinner with a full-brand SplashScreen:
- Dark navy radial gradient background with subtle dot-grid overlay
- App icon in a rounded badge with a glow effect
- BMS wordmark (48px Poppins Bold, letter-spaced) + tagline
- Staggered fade+scale-in animations (logo, text, tagline) via a single
  AnimationController
- Indeterminate progress bar + version number at the bottom
- splashReadyProvider returns immediately on web and on returning users
  (EULA already accepted); 2.5s minimum delay only on first desktop launch
- Replace double-wrapped logo container with single DecoratedBox (glow
  shadow only) so the SVG renders its own colours correctly - removes
  the white colorFilter that was turning everything blank
@hiranyasemindi hiranyasemindi merged commit 7e5b70c into master Jun 22, 2026
16 checks passed
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.

3 participants