Seeder: add --owner-email and --org-name CLI override flags#7870
Seeder: add --owner-email and --org-name CLI override flags#7870JaredSnider-Bitwarden wants to merge 1 commit into
Conversation
Adds two CLI flags to util/SeederUtility for parameterizing preset and
organization seed runs:
- --owner-email: override the default owner@<domain> owner address
- --org-name: override the fixture/preset-supplied organization name
Both flags route through a new SeederSettings record, picked up by
CreateOrganizationStep, CreateOwnerStep, and CreateRosterStep (the
roster path was a mid-development bug discovered during e2e testing
— presets with roster-declared owners bypass CreateOwnerStep, so the
override has to apply at the roster step too).
Adds a pre-flight DB check in RecipeOrchestrator that throws an
actionable InvalidOperationException when --owner-email collides with
an existing User.Email, rather than letting BulkCommitter fail with
a deep SQL unique-constraint exception. The check is skipped when
--mangle is enabled (per-run unique tag prevents collisions).
Test coverage: 43 new tests under test/SeederApi.IntegrationTest/
{Cli,Pipeline,Steps}/, including SQLite-backed integration tests
verifying both RecipeOrchestrator.Execute overloads invoke the guard.
Test project dependency: SeederApi.IntegrationTest now references
util/SeederUtility (was: util/Seeder only). This was added so the new
test/SeederApi.IntegrationTest/Cli/ tests can construct PresetArgs
and OrganizationArgs directly to exercise their Validate() and
ToOptions() logic. CommandDotNet appears as a transitive entry in
packages.lock.json as a consequence — it is the CLI parser SeederUtility
already uses (via PackageReference Include="CommandDotNet"), not a new
runtime dependency of the test process. Only types reachable from
PresetArgs/OrganizationArgs (the [Option] attribute) are touched at
test time; the actual CommandDotNet runtime is not invoked by tests.
Note: tests for the new override flags live in the existing
SeederApi.IntegrationTest project even though they are mostly
unit-scope. The project is a long-standing hybrid; a proper
test/Seeder.Test split is a separate follow-up.
|
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the addition of Verified the override-applied-once invariant holds: No findings at or above the confidence threshold. Test coverage is thorough, including integration tests against a real SQLite database exercising both Code Review DetailsNo issues found. Dependency Changes
Both arrive transitively via the new |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7870 +/- ##
=======================================
Coverage 61.21% 61.21%
=======================================
Files 2217 2217
Lines 98042 98042
Branches 8846 8846
=======================================
Hits 60013 60013
Misses 35916 35916
Partials 2113 2113 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|



🎟️ Tracking
n/a
📔 Objective
Add --owner-email and --org-name overrides to the seeder CLI (originally motivated by needing a known owner email for enterprise SSO testing) with a pre-flight collision check.
Detailed Changes
Library plumbing — overrides + pre-flight guard
util/Seeder/Options/OrganizationVaultOptions.csOwnerEmailinit-only property so theorganizationCLI command can forward an owner-email override through to the orchestrator.util/Seeder/Pipeline/SeederContextExtensions.csSeederSettingsrecord withOrgNameOverrideandOwnerEmailOverrideso override values can flow from the CLI through DI to any step.GetOrgNameOverride()/GetOwnerEmailOverride()extension methods onSeederContextso steps can read the overrides without coupling toSeederSettingsdirectly.--mangleand get a unique prefix ({mangleId}+local@domain) when mangled; explicitly notes this is not standard plus-addressing routing.util/Seeder/Pipeline/RecipeOrchestrator.csorgNameOverrideandownerEmailOverrideparameters to the presetExecuteoverload and threaded both intoSeederSettingsregistered in DI.OrganizationVaultOptions.OwnerEmailintoSeederSettings.OwnerEmailOverrideon the optionsExecuteoverload so theorganizationcommand path picks up the override.EnsureOwnerEmailUniquestatic helper that throwsInvalidOperationExceptionwhen--owner-emailcollides with an existingUser.Email, replacing the previous deep SQL unique-constraint failure with an actionable message that names the email and points at--mangle.Executeoverloads, before any crypto, preset reads, or DI setup — fails fast and avoids wasted work.util/Seeder/Recipes/OrganizationRecipe.csSeed(presetName, ...)with optionalorgNameandownerEmailparameters and forwarded them to the orchestrator.util/Seeder/Steps/CreateOrganizationStep.csSeederSettings.OrgNameOverridewhen set — guarded byIsNullOrWhiteSpaceso empty/whitespace overrides fall through to the original name.util/Seeder/Steps/CreateOwnerStep.csSeederSettings.OwnerEmailOverrideand uses it (instead of the defaultowner@<domain>) when constructing the owner user.--mangleinteraction stays consistent with the rest of the seeder.util/Seeder/Steps/CreateRosterStep.csSeederSettings.OwnerEmailOverrideto the first roster user withrole: "owner"— covers the case where presets with a roster-declared owner bypassCreateOwnerStepentirely (this was the mid-development bug surfaced during e2e testing).firstname.lastname@domainemail pattern, and group/collection lookups continue to resolve byFirstName.LastNameprefix so membership stays intact.CLI surface — new override flags
util/SeederUtility/Commands/PresetArgs.cs--org-nameflag for overriding the preset/fixture organization name.--owner-emailflag for overriding the default owner email; help text calls out theUser.Emailuniqueness constraint and points at--manglefor repeat runs.--owner-emailvalues that don't contain@, matching the existing arg-validation style.util/SeederUtility/Commands/OrganizationArgs.cs--owner-emailflag (org name is already controllable via-n) with the same uniqueness-aware help text.@-presence validation asPresetArgs.OwnerEmailintoToOptions()so it lands onOrganizationVaultOptions.OwnerEmail.util/SeederUtility/Commands/PresetCommand.csargs.OrgNameandargs.OwnerEmailtoOrganizationRecipe.Seed(...)so the preset path picks up the overrides.Tests — new structure under test/SeederApi.IntegrationTest
test/SeederApi.IntegrationTest/Cli/PresetArgsTests.cs(new)--owner-emailvalues without@.--org-nameis unvalidated and accepts arbitrary strings.test/SeederApi.IntegrationTest/Cli/OrganizationArgsTests.cs(new)PresetArgsfor--owner-emailvalidation.ToOptions()propagatesOwnerEmailtoOrganizationVaultOptions.OwnerEmailstays null inToOptions().test/SeederApi.IntegrationTest/Pipeline/SeederSettingsTests.cs(new)SeederSettingshas nullOrgNameOverrideandOwnerEmailOverride.SeederContextextension getters return the configured overrides from DI.test/SeederApi.IntegrationTest/Pipeline/RecipeOrchestratorTests.cs(new)EnsureOwnerEmailUniqueskips the existence predicate entirely for null/empty/whitespace overrides.InvalidOperationExceptionwith the override email +--manglehint when the predicate reports a collision.test/SeederApi.IntegrationTest/Pipeline/RecipeOrchestratorIntegrationTests.cs(new)IDisposablefixture wires a realDatabaseContextplusIDataProtectionProvider(required byOnModelCreatingfor User field converters).Executeoverload invokes the guard against a real DB and throws when a collidingUser.Emailexists.Executeoverload (used by theorganizationcommand) invokes the guard the same way — regression protection against silently removing the call from one overload.--mangleshort-circuits the guard end-to-end: with mangling on plus a colliding email, execution reachesSeedReaderand fails with a "not found" message rather than the guard's "already exists" message.null!forIMapperinSeederDependenciessince the guard fires before any AutoMapper usage; documented in the test as a deliberate regression-trip if the guard ever stops being first.test/SeederApi.IntegrationTest/Steps/SeederStepTestHelpers.cs(new)NewContext(defaultNoOpManglerService) andNewContextWithMangler(customIManglerService) so step tests can opt into mangling.PreloadOrganizationhelper that wires a real Rust SDK-keyed org into the context for steps that require one.StubSeedReaderfor tests that need to feed aSeedRosterorSeedOrganizationtoISeedReaderwithout filesystem/embedded-resource setup.test/SeederApi.IntegrationTest/Steps/CreateOrganizationStepTests.cs(new)FromParams.FromFixture.test/SeederApi.IntegrationTest/Steps/CreateOwnerStepTests.cs(new)owner@<domain>when no override is set.{8hex}+local@domainform rather than stored literal.owner@<domain>still gets mangled.OwnerOrgUseris linked to the org asOrganizationUserType.OwnerwithConfirmedstatus regardless of override path.test/SeederApi.IntegrationTest/Steps/CreateRosterStepTests.cs(new)firstname.lastname@domain.firstname.lastname@domain— only the owner is substituted.Everyonegroup still contains both users because lookups key byFirstName.LastNameprefix, not by email.firstname.lastname@domain.{8hex}+override@domainand non-owner users get{8hex}+prefix@domain.Build configuration
test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csprojProjectReferencetoutil/SeederUtilityso the newCli/*tests can constructPresetArgs/OrganizationArgsdirectly and exerciseValidate()/ToOptions().test/SeederApi.IntegrationTest/packages.lock.jsonCommandDotNetintroduced by theSeederUtilityproject reference; the CLI parser is not invoked at test time — only the[Option]attributes on the args classes are touched.📸 Screenshots
n/a my test after the changes worked though