Skip to content

Merge Android datastore into main Fleet datastore - #32233

Merged
cdcme merged 10 commits into
mainfrom
maint-31218-merge-android-datastore
Aug 25, 2025
Merged

Merge Android datastore into main Fleet datastore#32233
cdcme merged 10 commits into
mainfrom
maint-31218-merge-android-datastore

Conversation

@cdcme

@cdcme cdcme commented Aug 22, 2025

Copy link
Copy Markdown
Member

Resolves #31218

Summary

  • Moved Android MySQL files from server/mdm/android/mysql to server/datastore/mysql with android_ prefix
  • Updated import paths and function references throughout codebase
  • Consolidated schema generation to single file
  • Restored complete Android unit test coverage
  • Fixed Makefile MySQL test configuration

Test plan

  • All Android datastore tests passing
  • All Android MDM service tests passing
  • Android integration tests passing
  • Updated CI/CD configuration
  • Verified schema generator
  • Build verification successful

Summary by CodeRabbit

  • Refactor
    • Consolidated Android MDM data storage into the primary MySQL datastore, removing the separate Android schema and aligning naming.
  • Chores
    • Simplified CI and tooling to manage a single database schema and removed obsolete code-generation steps and hooks.
  • Tests
    • Migrated Android tests to the common datastore and updated mocks.
    • Added tests for device updates and enterprise retrieval by signup token.
    • Adjusted architecture/dependency checks to reflect the new layout.

No user-facing changes.

@codecov

codecov Bot commented Aug 22, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.06977% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.01%. Comparing base (2fd6a86) to head (5d0dd61).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
server/mdm/android/tests/testing_utils.go 72.72% 6 Missing ⚠️
server/datastore/mysql/android_hosts.go 66.66% 2 Missing ⚠️
server/datastore/mysql/android_mysql.go 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #32233      +/-   ##
==========================================
- Coverage   64.03%   64.01%   -0.03%     
==========================================
  Files        1988     1985       -3     
  Lines      194446   194360      -86     
  Branches     6551     6513      -38     
==========================================
- Hits       124522   124426      -96     
+ Misses      60225    60224       -1     
- Partials     9699     9710      +11     
Flag Coverage Δ
backend 65.28% <79.06%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@cdcme
cdcme marked this pull request as ready for review August 22, 2025 22:22
@cdcme
cdcme marked this pull request as draft August 22, 2025 23:07

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good overall.
The main tests are failing in CI. Please take a look.
Also, we can remove this mock:

//go:generate go run ../../../mock/mockimpl/impl.go -o datastore.go "ds *Datastore" "android.Datastore"

Comment thread server/mdm/android/arch_test.go
Comment thread server/mdm/android/service/service.go Outdated
@cdcme
cdcme marked this pull request as ready for review August 23, 2025 16:24
Comment thread server/mdm/android/tests/testing_utils.go Outdated
@getvictor

Copy link
Copy Markdown
Member

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@coderabbitai

coderabbitai Bot commented Aug 24, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Android datastore is merged into the main MySQL datastore: receiver/type renames to AndroidDatastore, constructor rename, wiring updates, tests refactored to use main datastore, Android-specific schema and testing utils removed, CI/hooks/Makefile/tools updated to drop Android schema handling, Android mocks replaced/removed, and dependency rules adjusted.

Changes

Cohort / File(s) Summary
CI and tooling adjustments
.github/workflows/test-db-changes.yml, git-hooks/backend/hooks/db-schema, Makefile, tools/dbutils/schema_generator.go, tools/ci/rules.go
Removed Android schema from triggers/checks and db utils; Makefile updates to test only main MySQL; dropped android import in CI rule; schema generator now dumps only primary schema and accepts a single path.
Datastore merge and wiring
server/datastore/mysql/android_mysql.go, server/datastore/mysql/android_hosts.go, server/datastore/mysql/android_enterprises.go, server/datastore/mysql/mysql.go, server/datastore/mysql/android.go
Renamed Android MySQL datastore type to AndroidDatastore with NewAndroidDatastore; updated receivers; adjusted internal wiring; call sites now use ds methods directly for device tx operations.
MySQL tests (datastore package)
server/datastore/mysql/android_device_test.go, server/datastore/mysql/android_enterprise_test.go
Renamed test suites; added device update test and helper txn wrappers; added signup-token retrieval test; adjusted enterprise test data (TopicID, SignupToken).
Android schema and MySQL test utils removal
server/mdm/android/mysql/schema.sql, server/mdm/android/mysql/testing_utils.go
Deleted Android-specific schema and MySQL testing utilities.
Android mocks and service/tests refactor
server/mdm/android/mock/android.go, server/mdm/android/mock/datastore.go, server/mdm/android/mock/datastore_setup.go, server/mdm/android/service/enterprises_test.go, server/mdm/android/tests/testing_utils.go, server/mdm/android/arch_test.go
Removed datastore mock generation and mock files; service tests now use ds_mock.Store and inline funcs; introduced routing wrappers in AndroidDSWithMock; adjusted arch dependency rules and ignored test package; disabled SSE timeouts in tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Test as Tests/Service
  participant DS as AndroidDSWithMock
  participant Mock as ds_mock.Store (mock)
  participant MySQL as mysql.AndroidDatastore
  participant DB as MySQL DB

  rect rgb(245,248,255)
  note over DS: Routing after merge (new)
  Test->>DS: AppConfig()
  DS->>Mock: AppConfig()
  Mock-->>DS: *fleet.AppConfig
  DS-->>Test: *fleet.AppConfig
  end

  rect rgb(245,255,245)
  note over DS,MySQL: Device ops via main datastore (new)
  Test->>DS: CreateDeviceTx(ctx, tx, device)
  DS->>MySQL: CreateDeviceTx(ctx, tx, device)
  MySQL->>DB: INSERT/UPSERT android_devices
  DB-->>MySQL: result/ID
  MySQL-->>DS: *android.Device
  DS-->>Test: *android.Device
  end

  rect rgb(255,248,245)
  note over DS,MySQL: Enterprise ops via main datastore (new)
  Test->>DS: GetEnterpriseBySignupToken(token)
  DS->>MySQL: GetEnterpriseBySignupToken(token)
  alt found
    MySQL->>DB: SELECT ...
    DB-->>MySQL: row
    MySQL-->>DS: *android.EnterpriseDetails
    DS-->>Test: details
  else not found
    MySQL-->>DS: notFound error
    DS-->>Test: notFound error
  end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Merge Android datastore code into main Fleet datastore (types, receivers, constructors) [#31218]
Merge/remove separate Android schema and related tooling/CI/hooks [#31218]
Update tests to use main datastore and remove Android-specific MySQL utils/mocks [#31218]

Possibly related PRs

Suggested labels

:ai

Suggested reviewers

  • lucasmrod

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch maint-31218-merge-android-datastore

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
server/datastore/mysql/android.go (1)

100-105: Critical fix needed: handle zero LastInsertId in NewAndroidHost

The current implementation of NewAndroidHost (server/datastore/mysql/android.go lines 97–105) uses result.LastInsertId() directly to set host.Host.ID. Under MySQL’s INSERT … ON DUPLICATE KEY UPDATE path, LastInsertId() returns 0 for updates, leaving host.Host.ID == 0 and causing all subsequent operations—display name upsert, label membership, CreateDeviceTx, etc.—to act on a nonexistent host (ID 0).

Apply this patch around line 100 in server/datastore/mysql/android.go:

-       id, _ := result.LastInsertId()
-       host.Host.ID = uint(id) // nolint:gosec
+       id, _ := result.LastInsertId()
+       if id == 0 {
+           // Duplicate key update path: fetch the existing host ID by node_key.
+           if err := sqlx.GetContext(ctx, tx, &id,
+               `SELECT id FROM hosts WHERE node_key = ?`, host.NodeKey); err != nil {
+               return ctxerr.Wrap(ctx, err, "lookup host id after upsert")
+           }
+       }
+       host.Host.ID = uint(id) // nolint:gosec

Additionally, add an integration test to cover this code path:

  • Pre-insert a host row with the same node_key.
  • Call NewAndroidHost with identical node_key.
  • Assert that returned host.Host.ID is nonzero and downstream operations succeed.
server/datastore/mysql/android_enterprises.go (1)

51-62: Ensure deterministic enterprise selection

I verified that enterprise_id is not constrained as UNIQUE in server/datastore/mysql/schema.sql, so

SELECTFROM android_enterprises WHERE enterprise_id != '' LIMIT 1

can return an arbitrary row when multiple entries exist. Please address this by either:

  • Enforcing uniqueness
    Add a UNIQUE index on enterprise_id (or on a generated “active” flag) in server/datastore/mysql/schema.sql to guarantee a single matching row.
  • Making selection deterministic
    Modify the query in server/datastore/mysql/android_enterprises.go (lines 51–62) to include ORDER BY, for example:
    - stmt := `SELECT id, enterprise_id FROM android_enterprises WHERE enterprise_id != '' LIMIT 1`
    + stmt := `SELECT id, enterprise_id
    +          FROM android_enterprises
    +          WHERE enterprise_id != ''
    +          ORDER BY id DESC
    +          LIMIT 1`

These changes ensure predictable behavior even if multiple non-empty enterprises exist.

server/mdm/android/tests/testing_utils.go (2)

235-241: Fix unused parameter (won’t compile) and document intent

The name parameter is now unused; Go rejects unused parameters.

Apply this minimal fix:

 func CreateNamedMySQLDS(t *testing.T, name string) *mysql.Datastore {
   if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
     t.Skip("MySQL tests are disabled")
   }
-	// use the standard Fleet datastore for Android integration tests
-	return mysql.CreateMySQLDS(t)
+	// use the standard Fleet datastore for Android integration tests
+	// dbName no longer needed after consolidation.
+	_ = name
+	return mysql.CreateMySQLDS(t)
 }

If uniqueness by dbName is still required elsewhere, we can switch to a named factory if available or derive a suffix from name when creating the DS. Let me know and I’ll propose that patch.


99-112: Fix Android tests to respect the provided DB name

The current CreateNamedMySQLDS in server/mdm/android/tests/testing_utils.go ignores the name parameter and always calls mysql.CreateMySQLDS(t), which (due to its fixed call‐stack offset) will resolve to a constant database name when invoked from this wrapper. This can cause parallel Android suites to stomp on each other’s database.

• Location: server/mdm/android/tests/testing_utils.go:235
• Issue: the wrapper must delegate the name through to the MySQL test harness rather than dropping it.

Suggested diff:

 func CreateNamedMySQLDS(t *testing.T, name string) *mysql.Datastore {
     if _, ok := os.LookupEnv("MYSQL_TEST"); !ok {
         t.Skip("MySQL tests are disabled")
     }
-    // use the standard Fleet datastore for Android integration tests
-    return mysql.CreateMySQLDS(t)
+    // forward the explicit DB name so each suite gets its own database
+    return mysql.CreateNamedMySQLDS(t, name)
 }

This ensures the Android integration tests run against a database named exactly by dbName, preserving isolation when suites execute in parallel.

♻️ Duplicate comments (2)
server/mdm/android/arch_test.go (1)

27-32: Arch rule scope is correct (allow tests, forbid prod deps on MySQL).

Allow-listing server/mdm/android/tests... while forbidding server/datastore/mysql... keeps prod Android code decoupled and still lets integration tests hit the main datastore. Matches the intent discussed in prior comments.

server/mdm/android/tests/testing_utils.go (1)

220-223: Avoid disabling timeouts entirely for SSE; use generous finite values instead

Zeroing Read/Write timeouts can mask hung tests. Prefer large, finite values that won’t interfere with SSE but still fail eventually.

Apply this diff:

-	// Disable timeouts for SSE connections in tests
-	server.Config.ReadTimeout = 0
-	server.Config.WriteTimeout = 0
+	// Use generous timeouts for SSE without risking indefinite hangs
+	server.Config.ReadTimeout = 2 * time.Minute
+	server.Config.WriteTimeout = 2 * time.Minute

Add the import:

 import (
 	"context"
 	"net/http"
 	"net/http/httptest"
 	"os"
 	"sync"
 	"testing"
+	"time"
🧹 Nitpick comments (15)
tools/dbutils/schema_generator.go (2)

43-45: Clarify the CLI usage error.

The panic message is vague. Make it explicit that exactly one argument (the dumpfile path) is required.

- if len(os.Args) != 2 {
-   panic("not enough arguments")
- }
+ if len(os.Args) != 2 {
+   panic("usage: schema_generator <dumpfile-path> (expects exactly 1 argument)")
+ }

90-91: Use non-executable file perms for schema.sql.

0o655 marks the file executable for group/others, which isn’t needed for a .sql dump. Prefer 0644.

- panicif(os.WriteFile(os.Args[1], stdoutBuf.Bytes(), 0o655))
+ panicif(os.WriteFile(os.Args[1], stdoutBuf.Bytes(), 0o644))
.github/workflows/test-db-changes.yml (1)

95-99: Fix typo in failure message.

Minor nit: “uncommited” → “uncommitted”.

- echo "❌ fail: uncommited changes in schema.sql"
+ echo "❌ fail: uncommitted changes in schema.sql"
git-hooks/backend/hooks/db-schema (1)

7-9: Polish the error output (path + typo).

The message still says “uncommited changes in schema.sql”. Consider including the full path and fix the typo to make CI/local failures clearer.

-    echo "❌ fail: uncommited changes in schema.sql"
-    echo "please run 'make dump-test-schema' and commit the changes"
+    echo "❌ fail: uncommitted changes in server/datastore/mysql/schema.sql"
+    echo "please run 'make dump-test-schema' and commit the changes"
server/mdm/android/service/enterprises_test.go (2)

153-179: Centralize mock wiring to reduce repetition and improve readability.

The inline assignments are fine, but extracting them into a small helper keeps tests concise and makes reuse trivial if other tests need the same setup.

 func InitCommonDSMocks() *AndroidMockDS {
   ds := AndroidMockDS{}
-  // Set up basic Android datastore mocks directly on the Fleet datastore mock
-  ds.Store.CreateEnterpriseFunc = func(ctx context.Context, _ uint) (uint, error) {
-    return 1, nil
-  }
-  ds.Store.UpdateEnterpriseFunc = func(ctx context.Context, enterprise *android.EnterpriseDetails) error {
-    return nil
-  }
-  ds.Store.GetEnterpriseFunc = func(ctx context.Context) (*android.Enterprise, error) {
-    return &android.Enterprise{}, nil
-  }
-  ds.Store.GetEnterpriseByIDFunc = func(ctx context.Context, ID uint) (*android.EnterpriseDetails, error) {
-    return &android.EnterpriseDetails{}, nil
-  }
-  ds.Store.GetEnterpriseBySignupTokenFunc = func(ctx context.Context, signupToken string) (*android.EnterpriseDetails, error) {
-    if signupToken == "signup_token" {
-      return &android.EnterpriseDetails{}, nil
-    }
-    return nil, &notFoundError{}
-  }
-  ds.Store.DeleteAllEnterprisesFunc = func(ctx context.Context) error {
-    return nil
-  }
-  ds.Store.DeleteOtherEnterprisesFunc = func(ctx context.Context, ID uint) error {
-    return nil
-  }
+  setAndroidStoreMocks(&ds)
 
   ds.Store.AppConfigFunc = func(_ context.Context) (*fleet.AppConfig, error) {
     return &fleet.AppConfig{}, nil
   }

Add this helper in the same file (outside the selected range):

func setAndroidStoreMocks(ds *AndroidMockDS) {
  ds.Store.CreateEnterpriseFunc = func(ctx context.Context, _ uint) (uint, error) { return 1, nil }
  ds.Store.UpdateEnterpriseFunc = func(ctx context.Context, _ *android.EnterpriseDetails) error { return nil }
  ds.Store.GetEnterpriseFunc = func(ctx context.Context) (*android.Enterprise, error) { return &android.Enterprise{}, nil }
  ds.Store.GetEnterpriseByIDFunc = func(ctx context.Context, _ uint) (*android.EnterpriseDetails, error) {
    return &android.EnterpriseDetails{}, nil
  }
  ds.Store.GetEnterpriseBySignupTokenFunc = func(ctx context.Context, signupToken string) (*android.EnterpriseDetails, error) {
    if signupToken == "signup_token" {
      return &android.EnterpriseDetails{}, nil
    }
    return nil, &notFoundError{}
  }
  ds.Store.DeleteAllEnterprisesFunc = func(ctx context.Context) error { return nil }
  ds.Store.DeleteOtherEnterprisesFunc = func(ctx context.Context, _ uint) error { return nil }
}

215-219: Optional: prefer prod-style NotFound for parity with service code paths.

The custom notFoundError type is adequate for tests because fleet.IsNotFound(err) hinges on IsNotFound(). If you want closer parity with production behavior, consider returning common_mysql.NotFound("Enterprise").WithName(signupToken) from the mock instead of a bespoke error type and drop notFoundError entirely.

server/datastore/mysql/android_device_test.go (2)

78-101: Stabilize the timestamp to avoid edge-case flakiness.

Using time.Now().UTC().Truncate(time.Millisecond) is usually fine; capturing it once improves determinism if assertions happen across DB roundtrips on busy CI agents.

-  created.AndroidPolicyID = ptr.Uint(5)
-  created.LastPolicySyncTime = ptr.Time(time.Now().UTC().Truncate(time.Millisecond))
+  created.AndroidPolicyID = ptr.Uint(5)
+  now := time.Now().UTC().Truncate(time.Millisecond)
+  created.LastPolicySyncTime = ptr.Time(now)

103-115: Minor readability: avoid reassigning the input parameter inside the tx closure.

Returning into a separate variable makes the ownership/lifetime clearer.

-func (ds *Datastore) createDevice(ctx context.Context, device *android.Device) (*android.Device, error) {
-  err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
-    var err error
-    device, err = ds.CreateDeviceTx(ctx, tx, device)
-    return err
-  })
-  if err != nil {
-    return nil, err
-  }
-  return device, nil
-}
+func (ds *Datastore) createDevice(ctx context.Context, device *android.Device) (*android.Device, error) {
+  var created *android.Device
+  err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
+    var err error
+    created, err = ds.CreateDeviceTx(ctx, tx, device)
+    return err
+  })
+  if err != nil {
+    return nil, err
+  }
+  return created, nil
+}
server/datastore/mysql/android_enterprise_test.go (1)

125-134: Add one more negative: empty/blank signup token.

Consider asserting that GetEnterpriseBySignupToken rejects empty or whitespace-only tokens to guard input validation and avoid accidental full scans.

Example:

_, err := ds.GetEnterpriseBySignupToken(testCtx(), "")
assert.True(t, fleet.IsNotFound(err))
server/datastore/mysql/android_mysql.go (2)

15-20: Add compile-time interface assertion for safety

To ensure future refactors don’t silently drop methods required by android.Datastore, add a compile-time assertion.

Apply this diff:

 type AndroidDatastore struct {
   logger  log.Logger
   primary *sqlx.DB
   replica fleet.DBReader // so it cannot be used to perform writes
 }
+
+// Ensure AndroidDatastore implements android.Datastore at compile time.
+var _ android.Datastore = (*AndroidDatastore)(nil)

34-39: Guard against nil replica in reader()

If ds.replica is nil (misconfiguration or certain test setups), reads will panic. Favor a safe fallback to primary.

Apply this diff:

-func (ds *AndroidDatastore) reader(ctx context.Context) fleet.DBReader {
+func (ds *AndroidDatastore) reader(ctx context.Context) fleet.DBReader {
   if ctxdb.IsPrimaryRequired(ctx) {
     return ds.primary
   }
-  return ds.replica
+  if ds.replica == nil {
+    return ds.primary
+  }
+  return ds.replica
 }
server/datastore/mysql/android_hosts.go (2)

39-50: Deletion choice is nondeterministic

Given the SELECT order is unspecified without ORDER BY, existing[0] could be any duplicate. The proposed ORDER BY ASC above makes you consistently delete the oldest row and update the newer one.


58-72: Insert looks correct; consider minor readability tweak

SQL is valid as-is. Optionally, keep VALUES placeholders on one line to reduce diff churn.

Apply this diff (cosmetic):

-	stmt := `INSERT INTO android_devices (host_id, device_id, enterprise_specific_id, android_policy_id, last_policy_sync_time) VALUES (?, ?, ?, ?,
-?)`
+	stmt := `INSERT INTO android_devices (host_id, device_id, enterprise_specific_id, android_policy_id, last_policy_sync_time)
+	         VALUES (?, ?, ?, ?, ?)`
server/datastore/mysql/android_enterprises.go (2)

14-23: CreateEnterprise path looks good

Insert is parameterized and returns the ID. Optional: check and return LastInsertId error explicitly.

Apply this diff (optional):

-	id, _ := res.LastInsertId()
+	id, err := res.LastInsertId()
+	if err != nil {
+		return 0, ctxerr.Wrap(ctx, err, "getting android_enterprises last insert ID")
+	}

64-83: Fix copy/paste error in wrapped message

Error text says “inserting enterprise” in an UPDATE path.

Apply this diff:

-	if err != nil {
-		return ctxerr.Wrap(ctx, err, "inserting enterprise")
-	}
+	if err != nil {
+		return ctxerr.Wrap(ctx, err, "updating enterprise")
+	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 87880ee and b249e84.

📒 Files selected for processing (20)
  • .github/workflows/test-db-changes.yml (1 hunks)
  • Makefile (2 hunks)
  • git-hooks/backend/hooks/db-schema (1 hunks)
  • server/datastore/mysql/android.go (2 hunks)
  • server/datastore/mysql/android_device_test.go (2 hunks)
  • server/datastore/mysql/android_enterprise_test.go (5 hunks)
  • server/datastore/mysql/android_enterprises.go (7 hunks)
  • server/datastore/mysql/android_hosts.go (5 hunks)
  • server/datastore/mysql/android_mysql.go (3 hunks)
  • server/datastore/mysql/mysql.go (1 hunks)
  • server/mdm/android/arch_test.go (1 hunks)
  • server/mdm/android/mock/android.go (0 hunks)
  • server/mdm/android/mock/datastore.go (0 hunks)
  • server/mdm/android/mock/datastore_setup.go (0 hunks)
  • server/mdm/android/mysql/schema.sql (0 hunks)
  • server/mdm/android/mysql/testing_utils.go (0 hunks)
  • server/mdm/android/service/enterprises_test.go (3 hunks)
  • server/mdm/android/tests/testing_utils.go (5 hunks)
  • tools/ci/rules.go (0 hunks)
  • tools/dbutils/schema_generator.go (1 hunks)
💤 Files with no reviewable changes (6)
  • server/mdm/android/mock/android.go
  • server/mdm/android/mysql/schema.sql
  • tools/ci/rules.go
  • server/mdm/android/mock/datastore_setup.go
  • server/mdm/android/mysql/testing_utils.go
  • server/mdm/android/mock/datastore.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

When reviewing SQL queries that are added or modified, ensure that appropriate filtering criteria are applied—especially when a query is intended to return data for a specific entity (e.g., a single host). Check for missing WHERE clauses or incorrect filtering that could lead to incorrect or non-deterministic results (e.g., returning the first row instead of the correct one). Flag any queries that may return unintended results due to lack of precise scoping.

Files:

  • tools/dbutils/schema_generator.go
  • server/datastore/mysql/android_enterprises.go
  • server/datastore/mysql/android.go
  • server/mdm/android/arch_test.go
  • server/datastore/mysql/android_device_test.go
  • server/datastore/mysql/android_hosts.go
  • server/datastore/mysql/mysql.go
  • server/mdm/android/service/enterprises_test.go
  • server/datastore/mysql/android_mysql.go
  • server/datastore/mysql/android_enterprise_test.go
  • server/mdm/android/tests/testing_utils.go
🧠 Learnings (4)
📚 Learning: 2025-08-01T15:08:16.858Z
Learnt from: sgress454
PR: fleetdm/fleet#31508
File: server/datastore/mysql/schema.sql:102-116
Timestamp: 2025-08-01T15:08:16.858Z
Learning: The schema.sql file in server/datastore/mysql/ is auto-generated from migrations for use with tests, so it cannot be manually edited. Any changes must be made through migrations.

Applied to files:

  • .github/workflows/test-db-changes.yml
  • Makefile
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet repository tests (server/datastore/mysql/labels_test.go and similar), using testing.T.Context() is valid because the project targets a recent Go version where testing.T.Context() exists. Do not suggest replacing t.Context() with context.Background() in this codebase.

Applied to files:

  • server/datastore/mysql/android_enterprise_test.go
  • server/mdm/android/tests/testing_utils.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: Fleet repo targets Go 1.24.5 (root go.mod), which supports testing.T.Context(). Do not flag usage of t.Context() or suggest replacing it with context.Background() in tests (e.g., server/datastore/mysql/labels_test.go Line 2031 and similar).

Applied to files:

  • server/datastore/mysql/android_enterprise_test.go
  • server/mdm/android/tests/testing_utils.go
📚 Learning: 2025-08-08T07:40:05.301Z
Learnt from: getvictor
PR: fleetdm/fleet#31726
File: server/datastore/mysql/labels_test.go:2031-2031
Timestamp: 2025-08-08T07:40:05.301Z
Learning: In fleetdm/fleet, tests may validly use testing.T.Context() when the module/toolchain targets Go 1.24+. Do not flag t.Context() usage in this codebase if go.mod/toolchain indicates Go >= 1.24.

Applied to files:

  • server/datastore/mysql/android_enterprise_test.go
🔇 Additional comments (24)
server/datastore/mysql/mysql.go (1)

269-269: Wiring Android datastore via NewAndroidDatastore looks good.

Embedding android.Datastore and initializing it here keeps construction localized without changing the public API surface.

server/datastore/mysql/android.go (2)

119-123: Switch to ds.CreateDeviceTx is correct and preserves transactional consistency.

This call uses the enclosing transaction (tx) and the unified Android datastore underneath. No issues spotted.


206-209: Update path uses ds.UpdateDeviceTx within the same transaction — good.

Keeping device updates in the same withRetryTxx scope avoids partial host/device state.

git-hooks/backend/hooks/db-schema (2)

6-6: LGTM: focus the diff on the single consolidated schema file.

Limiting the check to server/datastore/mysql/schema.sql aligns with the merge of Android schema into the main datastore.


6-11: No lingering Android schema references detected

Ran the provided ripgrep check against server/mdm/android/mysql/schema.sql and confirmed zero matches. All hooks, workflows, and targets have been updated accordingly.

Makefile (2)

235-235: Correct: single-target schema generation now matches the consolidated schema.

Updating only ./server/datastore/mysql/schema.sql via dbutils is consistent with the “one datastore, one schema.sql” direction and with the auto-generated rule in this repo.


310-310: Approve: MySQL test bucket trimmed and Android tests moved
The Makefile’s MYSQL_PKGS_TO_TEST := ./server/datastore/mysql/... is correct. Android datastore tests have been relocated under server/datastore/mysql. Verification confirms:

  • server/datastore/mysql/android_device_test.go
  • server/datastore/mysql/android_enterprise_test.go

No tests remain under server/mdm/android/mysql.

server/mdm/android/service/enterprises_test.go (1)

11-11: Good: use concrete android types in tests.

Pulling in server/mdm/android provides the canonical Enterprise and EnterpriseDetails types for the mocks.

server/datastore/mysql/android_device_test.go (1)

21-31: Rename and new case look good; coverage expanded to updates.

TestAndroidDevices consolidates device CRUD tests under the MySQL package and adds an explicit “UpdateDevice” case, which is valuable.

server/datastore/mysql/android_enterprise_test.go (2)

13-24: Good rename and additional coverage.

Renaming to TestAndroidEnterprises clarifies scope, and adding DeleteEnterprises plus GetEnterpriseBySignupToken exercises critical paths after the merge.


96-123: Scenario coverage is solid; keep the “no enterprise_id” case.

Validating DeleteOtherEnterprises with a record lacking enterprise_id is valuable to ensure filtering logic behaves as intended.

server/datastore/mysql/android_mysql.go (2)

22-29: Constructor rename looks good

NewAndroidDatastore wiring is straightforward and returns the android.Datastore interface as before. No concerns.


43-49: Writer and WithRetryTxx align with existing patterns

Returning the primary for writes and delegating retries to common_mysql is consistent with the main datastore.

server/datastore/mysql/android_hosts.go (4)

52-56: LGTM

Direct, parameterized delete by primary key.


94-97: LGTM

Thin wrapper is fine.


74-92: All db struct tags are correctly defined on android.Device
The Device struct in server/mdm/android/android.go (lines 43–48) includes matching db tags for all named parameters used in updateDevice (id, host_id, device_id, enterprise_specific_id, android_policy_id, last_policy_sync_time), so sqlx.Named will bind as expected.


12-37: Lock rows during duplicate-resolution to avoid races; make deletion deterministic

  • The SELECT used to detect existing devices should lock the matching rows within the transaction to prevent concurrent inserts/updates from causing flakiness or duplicate errors.
  • Add ORDER BY id ASC so that when two rows are returned you deterministically delete the oldest row.
  • Verified that server/datastore/mysql/schema.sql defines unique indexes on both device_id and enterprise_specific_id (indexes idx_android_devices_device_id and idx_android_devices_enterprise_specific_id), so locking these rows is safe and will target the correct records.

Apply this diff:

--- a/server/datastore/mysql/android_hosts.go
+++ b/server/datastore/mysql/android_hosts.go
@@ -12,7 +12,12 @@ func (ds *AndroidDatastore) CreateDeviceTx(ctx context.Context, tx sqlx.ExtContext
-	stmt := `SELECT id, device_id, enterprise_specific_id FROM android_devices WHERE device_id = ? OR enterprise_specific_id = ?`
+	stmt := `
+	SELECT id, device_id, enterprise_specific_id
+	  FROM android_devices
+	 WHERE device_id = ? OR enterprise_specific_id = ?
+	 ORDER BY id ASC
+	 FOR UPDATE`
server/datastore/mysql/android_enterprises.go (4)

25-36: LGTM

Scoped by ID with correct not-found handling.


38-49: LGTM

Signup-token lookup is properly scoped.


85-92: Caution: destructive delete

This deletes every row except the one provided. Ensure callers only pass the intended survivor and consider wrapping higher-level flows in a transaction to avoid partial state if follow-up steps fail.

Would you like me to wrap the call sites in a WithRetryTxx transaction and propose a patch?


94-101: LGTM

Appropriate for test cleanup paths.

server/mdm/android/tests/testing_utils.go (3)

44-47: Wrapper correctly routes AppConfig to the mock

This disambiguation avoids accidental calls into the real datastore. Good.


48-83: Device/enterprise wrappers look correct

Forwarding to the embedded real datastore is clear and avoids circular mocking. No issues.


176-178: LGTM

Closing via Datastore.Close() is the right companion to the constructor change.

Comment thread server/datastore/mysql/android_enterprise_test.go

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good overall.

The SSE changes are due to a flaky test, right? I recommend moving those to a separate PR so we don't mix different issues here.

@cdcme

cdcme commented Aug 24, 2025

Copy link
Copy Markdown
Member Author

Looks good overall.

The SSE changes are due to a flaky test, right? I recommend moving those to a separate PR so we don't mix different issues here.

@getvictor Exactly, yes. I've reverted those before going from draft —> open. 👍 Thanks!

@getvictor getvictor left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@georgekarrv georgekarrv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM ty!

@cdcme
cdcme merged commit 8bc8d01 into main Aug 25, 2025
39 checks passed
@cdcme
cdcme deleted the maint-31218-merge-android-datastore branch August 25, 2025 15:41
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.

Android: Merge Android datastore in the main Fleet datastore

4 participants