Skip to content

Seeder: add --owner-email and --org-name CLI override flags#7870

Open
JaredSnider-Bitwarden wants to merge 1 commit into
mainfrom
auth/seeder-improvements/add-owner-name-and-email-args
Open

Seeder: add --owner-email and --org-name CLI override flags#7870
JaredSnider-Bitwarden wants to merge 1 commit into
mainfrom
auth/seeder-improvements/add-owner-name-and-email-args

Conversation

@JaredSnider-Bitwarden

@JaredSnider-Bitwarden JaredSnider-Bitwarden commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🎟️ 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.cs

  1. Added OwnerEmail init-only property so the organization CLI command can forward an owner-email override through to the orchestrator.

util/Seeder/Pipeline/SeederContextExtensions.cs

  1. Extended the SeederSettings record with OrgNameOverride and OwnerEmailOverride so override values can flow from the CLI through DI to any step.
  2. Added GetOrgNameOverride() / GetOwnerEmailOverride() extension methods on SeederContext so steps can read the overrides without coupling to SeederSettings directly.
  3. Updated the xmldoc to describe the mangling interaction precisely — values are literal without --mangle and get a unique prefix ({mangleId}+local@domain) when mangled; explicitly notes this is not standard plus-addressing routing.

util/Seeder/Pipeline/RecipeOrchestrator.cs

  1. Added orgNameOverride and ownerEmailOverride parameters to the preset Execute overload and threaded both into SeederSettings registered in DI.
  2. Mapped OrganizationVaultOptions.OwnerEmail into SeederSettings.OwnerEmailOverride on the options Execute overload so the organization command path picks up the override.
  3. Added a new EnsureOwnerEmailUnique static helper that throws InvalidOperationException when --owner-email collides with an existing User.Email, replacing the previous deep SQL unique-constraint failure with an actionable message that names the email and points at --mangle.
  4. Invoked the guard as the first statement in both Execute overloads, before any crypto, preset reads, or DI setup — fails fast and avoids wasted work.
  5. Guard short-circuits when mangling is enabled or the override is null/whitespace, since the mangler's per-run tag already guarantees uniqueness in those cases.

util/Seeder/Recipes/OrganizationRecipe.cs

  1. Extended Seed(presetName, ...) with optional orgName and ownerEmail parameters and forwarded them to the orchestrator.

util/Seeder/Steps/CreateOrganizationStep.cs

  1. After resolving the org name from a fixture or params, replaces it with SeederSettings.OrgNameOverride when set — guarded by IsNullOrWhiteSpace so empty/whitespace overrides fall through to the original name.

util/Seeder/Steps/CreateOwnerStep.cs

  1. Reads SeederSettings.OwnerEmailOverride and uses it (instead of the default owner@<domain>) when constructing the owner user.
  2. Override still flows through the mangler so --mangle interaction stays consistent with the rest of the seeder.

util/Seeder/Steps/CreateRosterStep.cs

  1. Applies SeederSettings.OwnerEmailOverride to the first roster user with role: "owner" — covers the case where presets with a roster-declared owner bypass CreateOwnerStep entirely (this was the mid-development bug surfaced during e2e testing).
  2. Non-owner roster users keep their firstname.lastname@domain email pattern, and group/collection lookups continue to resolve by FirstName.LastName prefix so membership stays intact.
CLI surface — new override flags

util/SeederUtility/Commands/PresetArgs.cs

  1. Added --org-name flag for overriding the preset/fixture organization name.
  2. Added --owner-email flag for overriding the default owner email; help text calls out the User.Email uniqueness constraint and points at --mangle for repeat runs.
  3. Added validation rejecting --owner-email values that don't contain @, matching the existing arg-validation style.

util/SeederUtility/Commands/OrganizationArgs.cs

  1. Added --owner-email flag (org name is already controllable via -n) with the same uniqueness-aware help text.
  2. Added the same @-presence validation as PresetArgs.
  3. Wired OwnerEmail into ToOptions() so it lands on OrganizationVaultOptions.OwnerEmail.

util/SeederUtility/Commands/PresetCommand.cs

  1. Forwarded args.OrgName and args.OwnerEmail to OrganizationRecipe.Seed(...) so the preset path picks up the overrides.
Tests — new structure under test/SeederApi.IntegrationTest

test/SeederApi.IntegrationTest/Cli/PresetArgsTests.cs (new)

  1. Theory rejecting --owner-email values without @.
  2. Theory accepting valid emails plus null/empty/whitespace values (treated as not provided).
  3. Fact confirming --org-name is unvalidated and accepts arbitrary strings.

test/SeederApi.IntegrationTest/Cli/OrganizationArgsTests.cs (new)

  1. Theories mirroring PresetArgs for --owner-email validation.
  2. Fact confirming ToOptions() propagates OwnerEmail to OrganizationVaultOptions.
  3. Fact confirming null OwnerEmail stays null in ToOptions().

test/SeederApi.IntegrationTest/Pipeline/SeederSettingsTests.cs (new)

  1. Default SeederSettings has null OrgNameOverride and OwnerEmailOverride.
  2. SeederContext extension getters return the configured overrides from DI.
  3. Extension getters return null when overrides are unset.

test/SeederApi.IntegrationTest/Pipeline/RecipeOrchestratorTests.cs (new)

  1. Theory verifying EnsureOwnerEmailUnique skips the existence predicate entirely for null/empty/whitespace overrides.
  2. Fact verifying the predicate is skipped when mangling is enabled, regardless of override value.
  3. Fact verifying no throw when the predicate reports no collision.
  4. Fact verifying InvalidOperationException with the override email + --mangle hint when the predicate reports a collision.
  5. Fact verifying the predicate receives the override email verbatim.

test/SeederApi.IntegrationTest/Pipeline/RecipeOrchestratorIntegrationTests.cs (new)

  1. SQLite-backed IDisposable fixture wires a real DatabaseContext plus IDataProtectionProvider (required by OnModelCreating for User field converters).
  2. Fact proving the preset Execute overload invokes the guard against a real DB and throws when a colliding User.Email exists.
  3. Fact proving the options Execute overload (used by the organization command) invokes the guard the same way — regression protection against silently removing the call from one overload.
  4. Fact proving --mangle short-circuits the guard end-to-end: with mangling on plus a colliding email, execution reaches SeedReader and fails with a "not found" message rather than the guard's "already exists" message.
  5. Uses null! for IMapper in SeederDependencies since 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)

  1. Shared harness exposing NewContext (default NoOpManglerService) and NewContextWithMangler (custom IManglerService) so step tests can opt into mangling.
  2. PreloadOrganization helper that wires a real Rust SDK-keyed org into the context for steps that require one.
  3. StubSeedReader for tests that need to feed a SeedRoster or SeedOrganization to ISeedReader without filesystem/embedded-resource setup.

test/SeederApi.IntegrationTest/Steps/CreateOrganizationStepTests.cs (new)

  1. Default (no override) keeps the fixture/param name.
  2. Override replaces the name from FromParams.
  3. Whitespace override is ignored — falls back to the fixture/param name.
  4. Override replaces the fixture-supplied name when using FromFixture.

test/SeederApi.IntegrationTest/Steps/CreateOwnerStepTests.cs (new)

  1. Default owner email is owner@<domain> when no override is set.
  2. Override replaces the owner email.
  3. With mangling enabled, the override is mangled to {8hex}+local@domain form rather than stored literal.
  4. Baseline: with mangling enabled and no override, the default owner@<domain> still gets mangled.
  5. OwnerOrgUser is linked to the org as OrganizationUserType.Owner with Confirmed status regardless of override path.

test/SeederApi.IntegrationTest/Steps/CreateRosterStepTests.cs (new)

  1. Without override, the first owner-role roster user gets firstname.lastname@domain.
  2. With override, the first owner-role roster user gets the override email.
  3. With override, non-owner users keep firstname.lastname@domain — only the owner is substituted.
  4. With override, group membership resolves correctly: Everyone group still contains both users because lookups key by FirstName.LastName prefix, not by email.
  5. With override and two owner-role users in the roster, only the first one receives the override; the second falls back to firstname.lastname@domain.
  6. With override + mangling enabled, the owner email becomes {8hex}+override@domain and non-owner users get {8hex}+prefix@domain.
Build configuration

test/SeederApi.IntegrationTest/SeederApi.IntegrationTest.csproj

  1. Added ProjectReference to util/SeederUtility so the new Cli/* tests can construct PresetArgs/OrganizationArgs directly and exercise Validate() / ToOptions().

test/SeederApi.IntegrationTest/packages.lock.json

  1. Lock file auto-updated to reflect the new transitive dependency on CommandDotNet introduced by the SeederUtility project 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

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.
@JaredSnider-Bitwarden JaredSnider-Bitwarden added the t:feature Change Type - Feature Development label Jun 25, 2026
@JaredSnider-Bitwarden JaredSnider-Bitwarden marked this pull request as ready for review June 25, 2026 14:57
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the addition of --owner-email and --org-name override flags to the Seeder CLI, including the library plumbing (SeederSettings, RecipeOrchestrator, the CreateOwnerStep/CreateRosterStep/CreateOrganizationStep steps), the new EnsureOwnerEmailUnique pre-flight guard, the CLI surface, and the new test suite under test/SeederApi.IntegrationTest. This is dev/test seeder tooling with no production, crypto, auth, or vault-data impact.

Verified the override-applied-once invariant holds: CreateOwnerStep only runs when the roster declares no owner (!HasRosterOwner in PresetLoader), so the override lands on exactly one user. The pre-flight collision check correctly mirrors storage (emails stored verbatim, no normalization) and correctly short-circuits when mangling is enabled. Group/collection lookups remain keyed by FirstName.LastName prefix, unaffected by the email override. The new CommandDotNet/Spectre.Console lock-file entries are transitive from the new SeederUtility project reference — both are already approved dependencies in that project, so no AppSec finding applies.

No findings at or above the confidence threshold. Test coverage is thorough, including integration tests against a real SQLite database exercising both Execute overloads.

Code Review Details

No issues found.

Dependency Changes

Package Change Ecosystem
CommandDotNet New transitive (7.0.5) in test project NuGet
Spectre.Console New transitive (0.55.2) in test project NuGet

Both arrive transitively via the new SeederUtility project reference and are already used by util/SeederUtility; not net-new to the codebase.

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 61.21%. Comparing base (ff815e7) to head (b8973dc).
⚠️ Report is 4 commits behind head on main.

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.
📢 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant