Repository navigation
⭐ Account Customer Service
A small REST CRUD service owning accounts and their meter registrations — the missing piece that let Phase 3 finally resolve the MeterId-not-AccountId deviation carried since Phase 1.
-
Account—Id(Guid),ContactWebhookUrl(where Notification Service delivers this account's invoice webhooks) -
Meter—MeterId(string),AccountId(Guid FK →Account.Id, cascade delete),StreetName(string),StreetNumber(int) — a real foreign key, a genuinely separate bounded context from Usage Aggregation/Billing (its ownaccountsdb, not a shared database) -
AccountSeeder— idempotently registers one account per configured meter at startup, usingCityBlockMeterIdFactoryfrom the sharedGridPulse.Sharedlibrary (extracted from this project's and Meter Simulator's originally-duplicated copies once that duplication turned out to have no compensating safety net — seedocs/architecture.md) -
AccountSeedOptions—StreetName/StartingAddress/BuildingsPerSide(the same site parameters Meter Simulator receives) plusNotificationWebhookUrl, shared across every seeded account for live verification — all fail-fast validated, same discipline as Meter Simulator's config - REST endpoints: full CRUD on
/accounts, create/read/delete on/meters(registration doesn't need an update operation)
Unlike Usage Aggregation and Billing, which historically shared one physical Postgres database, Account/Customer Service gets its own accountsdb on the same Aspire-managed Postgres server — a real architectural boundary, not just a different schema.
The first live run threw Could not create HTTP health check for resource 'account-customer' as no endpoint was found matching one of the specified names: https, http. Root cause: the hand-scaffolded project had no Properties/launchSettings.json — every other service in this repo has one because it was generated by dotnet new tooling, this one was hand-written, and Aspire's AddProject<T>() reads that file to discover a project resource's HTTP endpoints. Fixed by adding a proper launchSettings.json and pinning the health check to the http endpoint explicitly. Re-verified live: account-customer reaches Healthy, and accountsdb shows real seeded accounts/meters matching Meter Simulator's configured city block exactly — issue #20.
AccountCustomer.Tests covers CityBlockMeterIdFactory and AccountSeeder directly, but the REST endpoints themselves aren't covered by WebApplicationFactory integration tests — building the DI-override plumbing to swap out a real Postgres-backed startup (migration + seeding) for a thin CRUD wrapper wasn't worth it. Instead, POST /accounts and POST /meters get real coverage from CoreLoopSmokeTests, which calls both live against a running Account/Customer Service before producing its synthetic reading.
Seeded accounts/meters confirmed directly in Postgres matching Meter Simulator's real 28-meter city block. Later, as part of Phase 3's full end-to-end verification, POST /accounts and POST /meters were exercised live for real (not simulated) by CoreLoopSmokeTests, and the resulting AccountId was confirmed flowing correctly all the way through Usage Aggregation and Billing to a real webhook delivery.
4 xUnit tests: AccountSeeder (creates one account+meter per configured meter, sets ContactWebhookUrl from options, idempotent on a second call, every meter maps to a real account). CityBlockMeterIdFactory's own tests moved to Shared.Tests when it was extracted into GridPulse.Shared.