refactor(cloud): drop the svc suffix — bare package names + spelled-out test helpers
One name per thing, no compound-word cruft. The `svc` suffix was never a real package (zero `package *svc`) — only import aliases and abbreviated test helpers. - Import aliases → bare package names: plansvc→plan (commerceclient), captablesvc→captable + dataroomsvc→dataroom (company/adapters). No stutter, no alias where the bare name is unambiguous. - Test helpers spelled out: fakeSvc→fakeService, testSvc→testService, newSvc→newService — across admin/agents/deploy/domain/functions/ingress/ integrations/ml/platform/provisioning/storage/wallets tests, callers updated in-package. - Stale `// Package …svc` doc-comment prose corrected to the real package name (exec/iam/plugin/pricing/product/provisioning/sync/tasks). Naming only — no logic change. go build + test-compile green on all 21 packages. Note: the clients/team package (filesSvc/fsvc rename) is excluded here — it has concurrent in-progress work; its svc cleanup lands with that change.
This commit is contained in:
@@ -24,16 +24,16 @@ import (
|
||||
// mount builds a zip app with admin mounted against the given upstream bases,
|
||||
// and returns a `do` helper that issues test requests through the whole app.
|
||||
func mount(t *testing.T, iamURL, commerceURL, healthURL string) func(method, path string, hdr map[string]string) (*http.Response, []byte) {
|
||||
do, _, _ := mountSvc(t, iamURL, commerceURL, healthURL)
|
||||
do, _, _ := mountService(t, iamURL, commerceURL, healthURL)
|
||||
return do
|
||||
}
|
||||
|
||||
// mountSvc is mount but also returns the underlying cloud.Service[state] (so finance tests can swap
|
||||
// mountService is mount but also returns the underlying cloud.Service[state] (so finance tests can swap
|
||||
// in a fake DigitalOcean client, and the cockpit tests can attach an audit store)
|
||||
// AND the raw fiber app (so tests that need a request BODY can drive it directly —
|
||||
// the returned `do` sends a nil body). The handlers read s.* live at request time,
|
||||
// so an override before issuing a request takes effect.
|
||||
func mountSvc(t *testing.T, iamURL, commerceURL, healthURL string) (func(method, path string, hdr map[string]string) (*http.Response, []byte), *cloud.Service[core.State], *fiber.App) {
|
||||
func mountService(t *testing.T, iamURL, commerceURL, healthURL string) (func(method, path string, hdr map[string]string) (*http.Response, []byte), *cloud.Service[core.State], *fiber.App) {
|
||||
t.Helper()
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
s := &cloud.Service[core.State]{State: core.State{
|
||||
|
||||
@@ -203,7 +203,7 @@ func newCockpitFakes(t *testing.T) *cockpitFakes {
|
||||
}
|
||||
}))
|
||||
|
||||
_, s, fa := mountSvc(t, f.iam.URL, f.commerce.URL, "")
|
||||
_, s, fa := mountService(t, f.iam.URL, f.commerce.URL, "")
|
||||
f.service = s
|
||||
f.do = func(method, path string, hdr map[string]string, body string) (*http.Response, []byte) {
|
||||
t.Helper()
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
// The finance PURE-math derivation tests (ComputeFinance / AvgDailyBurnCents) live with
|
||||
// the handler in clients/admin/finance. These are the INTEGRATION tests that drive GET
|
||||
// /v1/admin/finance through the shared admin mount harness (mountSvc + fake IAM/commerce/DO).
|
||||
// /v1/admin/finance through the shared admin mount harness (mountService + fake IAM/commerce/DO).
|
||||
|
||||
// newFakeDO serves the DO billing API with fixed decimal-dollar strings so the
|
||||
// finance aggregation is deterministic. account_balance is NEGATIVE (credit held).
|
||||
@@ -49,7 +49,7 @@ func TestFinance_RealAggregation(t *testing.T) {
|
||||
do := newFakeDO()
|
||||
defer do.Close()
|
||||
|
||||
doReq, s, _ := mountSvc(t, iam.server.URL, commerce.URL, "")
|
||||
doReq, s, _ := mountService(t, iam.server.URL, commerce.URL, "")
|
||||
s.State.DO = digitalocean.NewWithBase(do.URL, "test-do-token") // configured DO client
|
||||
admin := map[string]string{
|
||||
"X-User-IsAdmin": "true", "X-Org-Id": "admin",
|
||||
@@ -151,7 +151,7 @@ func TestFinance_HonestUnconfiguredDO(t *testing.T) {
|
||||
commerce := newFakeCommerceFinance()
|
||||
defer commerce.Close()
|
||||
|
||||
doReq, _, _ := mountSvc(t, iam.server.URL, commerce.URL, "") // s.do already has empty token → unconfigured
|
||||
doReq, _, _ := mountService(t, iam.server.URL, commerce.URL, "") // s.do already has empty token → unconfigured
|
||||
admin := map[string]string{"X-User-IsAdmin": "true", "X-Org-Id": "admin"}
|
||||
|
||||
resp, body := doReq("GET", "/v1/admin/finance", admin)
|
||||
@@ -212,7 +212,7 @@ func TestFinance_RevenueSourceDown_NoFabrication(t *testing.T) {
|
||||
defer commerce.Close()
|
||||
|
||||
// IAM points nowhere reachable → listOrgs errors; commerce /v1/costs still 200s.
|
||||
doReq, _, _ := mountSvc(t, "http://127.0.0.1:0", commerce.URL, "")
|
||||
doReq, _, _ := mountService(t, "http://127.0.0.1:0", commerce.URL, "")
|
||||
admin := map[string]string{"X-User-IsAdmin": "true", "X-Org-Id": "admin"}
|
||||
|
||||
resp, body := doReq("GET", "/v1/admin/finance", admin)
|
||||
|
||||
@@ -48,9 +48,9 @@ func (c *countingAI) Embed(_ context.Context, _ *types.EmbedRequest) ([][]float3
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// schedSvc builds a Service + scheduler with NO billing (gate allows) and the given
|
||||
// schedService builds a Service + scheduler with NO billing (gate allows) and the given
|
||||
// AI, seeded with the supplied agents. Returns the scheduler for direct tick().
|
||||
func schedSvc(t *testing.T, ai types.AIClient, seed ...Agent) *scheduler {
|
||||
func schedService(t *testing.T, ai types.AIClient, seed ...Agent) *scheduler {
|
||||
t.Helper()
|
||||
s := &cloud.Service[state]{Base: cloud.Base{Log: luxlog.New("test")}, State: state{store: testStore(t), ai: ai}}
|
||||
for _, a := range seed {
|
||||
@@ -83,7 +83,7 @@ func waitFor(cond func() bool) bool {
|
||||
// run; a tick at a non-matching minute launches none.
|
||||
func TestSchedulerFiresDueAgent(t *testing.T) {
|
||||
ai := &countingAI{}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "*/5 * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "*/5 * * * *"))
|
||||
ctx := context.Background()
|
||||
|
||||
sc.tick(ctx, at(t, "2026-07-01 12:36")) // 36 not multiple of 5 -> no fire
|
||||
@@ -102,7 +102,7 @@ func TestSchedulerFiresDueAgent(t *testing.T) {
|
||||
// like an HTTP run — the scheduler shares runAgent.
|
||||
func TestSchedulerRecordsRun(t *testing.T) {
|
||||
ai := &countingAI{}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
ctx := context.Background()
|
||||
sc.tick(ctx, at(t, "2026-07-01 12:00"))
|
||||
if !waitFor(func() bool {
|
||||
@@ -119,7 +119,7 @@ func TestSchedulerRecordsRun(t *testing.T) {
|
||||
// tick fires; the immediately-following matching tick is skipped (backoff=1).
|
||||
func TestSchedulerBackoffOnFailure(t *testing.T) {
|
||||
ai := &countingAI{fail: true}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
ctx := context.Background()
|
||||
|
||||
sc.tick(ctx, at(t, "2026-07-01 12:00"))
|
||||
@@ -152,7 +152,7 @@ func TestSchedulerBackoffOnFailure(t *testing.T) {
|
||||
// second matching tick while it is in flight does NOT start a second run.
|
||||
func TestSchedulerConcurrencyCap(t *testing.T) {
|
||||
ai := &countingAI{block: make(chan struct{})}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
ctx := context.Background()
|
||||
|
||||
sc.tick(ctx, at(t, "2026-07-01 12:00")) // starts run #1, which blocks
|
||||
@@ -257,7 +257,7 @@ func TestSchedulerGatesUnfundedRun(t *testing.T) {
|
||||
// path that lets Shutdown close the store safely.
|
||||
func TestSchedulerStopDrainsCleanly(t *testing.T) {
|
||||
ai := &countingAI{}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc.start()
|
||||
// Fire one run via a direct tick, then stop — stop must return after drain.
|
||||
sc.tick(context.Background(), at(t, "2026-07-01 12:00"))
|
||||
@@ -286,7 +286,7 @@ func TestSchedulerStopDrainsCleanly(t *testing.T) {
|
||||
// rather than waiting the full runTimeout.
|
||||
func TestSchedulerStopHonorsDeadline(t *testing.T) {
|
||||
ai := &countingAI{block: make(chan struct{})}
|
||||
sc := schedSvc(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc := schedService(t, ai, longRunning("acme", "cron", "* * * * *"))
|
||||
sc.start()
|
||||
sc.tick(context.Background(), at(t, "2026-07-01 12:00"))
|
||||
if !waitFor(func() bool { return ai.count() == 1 }) {
|
||||
@@ -310,7 +310,7 @@ func TestSchedulerStopHonorsDeadline(t *testing.T) {
|
||||
func TestSchedulerOnlyLongRunning(t *testing.T) {
|
||||
ai := &countingAI{}
|
||||
one := mk("acme", "one") // one-shot default, no schedule
|
||||
sc := schedSvc(t, ai, one)
|
||||
sc := schedService(t, ai, one)
|
||||
sc.tick(context.Background(), at(t, "2026-07-01 12:00"))
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
if ai.count() != 0 {
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
plansvc "github.com/hanzoai/cloud/clients/plan"
|
||||
"github.com/hanzoai/cloud/clients/plan"
|
||||
"github.com/hanzoai/cloud/types"
|
||||
commercemod "github.com/hanzoai/commerce"
|
||||
"github.com/hanzoai/commerce/datastore"
|
||||
@@ -134,7 +134,7 @@ func (c *inProcessClient) CheckEntitlement(ctx context.Context, orgID, productID
|
||||
if slug == "" {
|
||||
continue // no resolvable plan tier on this sub — cannot grant from it
|
||||
}
|
||||
_, features, found, ferr := plansvc.LicenseEntitlement(ctx, slug)
|
||||
_, features, found, ferr := plan.LicenseEntitlement(ctx, slug)
|
||||
if ferr != nil {
|
||||
// MACHINERY failure (plans vocabulary unavailable): cannot resolve features
|
||||
// ⇒ cannot verify ⇒ fail closed. Never deny-by-guess on an outage.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/hanzoai/cloud"
|
||||
inproc "github.com/hanzoai/cloud/clients/commerceclient"
|
||||
plansvc "github.com/hanzoai/cloud/clients/plan"
|
||||
"github.com/hanzoai/cloud/clients/plan"
|
||||
commercemod "github.com/hanzoai/commerce"
|
||||
"github.com/hanzoai/commerce/billing/grant"
|
||||
"github.com/hanzoai/commerce/datastore"
|
||||
@@ -27,8 +27,8 @@ func mountPlansVocab(t *testing.T) {
|
||||
t.Helper()
|
||||
plansOnce.Do(func() {
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test-plans")})
|
||||
if err := plansvc.Mount(app, cloud.Deps{Logger: luxlog.New("test-plans"), Brand: "hanzo"}); err != nil {
|
||||
t.Fatalf("plansvc.Mount: %v", err)
|
||||
if err := plan.Mount(app, cloud.Deps{Logger: luxlog.New("test-plans"), Brand: "hanzo"}); err != nil {
|
||||
t.Fatalf("plan.Mount: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+15
-15
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
captablesvc "github.com/hanzoai/cloud/clients/captable"
|
||||
dataroomsvc "github.com/hanzoai/cloud/clients/dataroom"
|
||||
"github.com/hanzoai/cloud/clients/captable"
|
||||
"github.com/hanzoai/cloud/clients/dataroom"
|
||||
)
|
||||
|
||||
// adapters.go wires the company provider interfaces to the REAL sibling subsystems
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
type dataroomSink struct{}
|
||||
|
||||
func (dataroomSink) Ingest(ctx context.Context, org, name, contentType string, data []byte) (string, error) {
|
||||
return dataroomsvc.Ingest(ctx, org, name, contentType, data)
|
||||
return dataroom.Ingest(ctx, org, name, contentType, data)
|
||||
}
|
||||
|
||||
// ---- CapTable → captable ----
|
||||
@@ -37,7 +37,7 @@ type captableAdapter struct{}
|
||||
|
||||
// SetIncorporation records the entity kind on the tenant's canonical company row.
|
||||
func (captableAdapter) SetIncorporation(ctx context.Context, org, companyName, incType, country, state string) error {
|
||||
return captablesvc.SetIncorporation(ctx, org, companyName, incType, country, state)
|
||||
return captable.SetIncorporation(ctx, org, companyName, incType, country, state)
|
||||
}
|
||||
|
||||
// SeedFounders writes the founding allocation into the cap table: it sets the
|
||||
@@ -46,24 +46,24 @@ func (captableAdapter) SetIncorporation(ctx context.Context, org, companyName, i
|
||||
// the 10M pool). Idempotent enough to re-run: stakeholders dedupe by email, the
|
||||
// share class is ensured-by-name, and certificate ids are per-founder.
|
||||
func (a captableAdapter) SeedFounders(ctx context.Context, org, companyName string, founders []Founder) error {
|
||||
holders := make([]captablesvc.StakeholderInput, 0, len(founders))
|
||||
holders := make([]captable.StakeholderInput, 0, len(founders))
|
||||
for _, fo := range founders {
|
||||
holders = append(holders, captablesvc.StakeholderInput{
|
||||
holders = append(holders, captable.StakeholderInput{
|
||||
Name: fo.Name, Email: fo.Email,
|
||||
StakeholderType: "INDIVIDUAL", CurrentRelationship: "FOUNDER",
|
||||
})
|
||||
}
|
||||
if _, err := captablesvc.AddStakeholders(ctx, org, holders); err != nil {
|
||||
if _, err := captable.AddStakeholders(ctx, org, holders); err != nil {
|
||||
return fmt.Errorf("seed founders: add stakeholders: %w", err)
|
||||
}
|
||||
classID, err := captablesvc.EnsureShareClass(ctx, org, captablesvc.ShareClassInput{
|
||||
classID, err := captable.EnsureShareClass(ctx, org, captable.ShareClassInput{
|
||||
Name: "Common", ClassType: "COMMON", InitialSharesAuthorized: foundingAuthorizedShares,
|
||||
VotesPerShare: 1, ParValue: 0.0001, PricePerShare: 0.0001,
|
||||
})
|
||||
if err != nil || classID == "" {
|
||||
return fmt.Errorf("seed founders: ensure common class: %w", err)
|
||||
}
|
||||
ids, err := captablesvc.StakeholderIDsByEmail(ctx, org)
|
||||
ids, err := captable.StakeholderIDsByEmail(ctx, org)
|
||||
if err != nil {
|
||||
return fmt.Errorf("seed founders: resolve ids: %w", err)
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (a captableAdapter) SeedFounders(ctx context.Context, org, companyName stri
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := captablesvc.IssueShares(ctx, org, captablesvc.ShareInput{
|
||||
if err := captable.IssueShares(ctx, org, captable.ShareInput{
|
||||
StakeholderID: sid, ShareClassID: classID, CertificateID: fmt.Sprintf("%s-%d", cert, i+1),
|
||||
Quantity: int64(fo.EquityBps) * sharesPerBps, Status: "ACTIVE",
|
||||
}); err != nil {
|
||||
@@ -89,7 +89,7 @@ func (a captableAdapter) SeedFounders(ctx context.Context, org, companyName stri
|
||||
// AddStakeholders maps company stakeholders (used by the cap-table import path) to
|
||||
// the captable contract.
|
||||
func (captableAdapter) AddStakeholders(ctx context.Context, org string, holders []Stakeholder) (int, error) {
|
||||
in := make([]captablesvc.StakeholderInput, 0, len(holders))
|
||||
in := make([]captable.StakeholderInput, 0, len(holders))
|
||||
for _, h := range holders {
|
||||
st := h.StakeholderType
|
||||
if st == "" {
|
||||
@@ -99,17 +99,17 @@ func (captableAdapter) AddStakeholders(ctx context.Context, org string, holders
|
||||
if rel == "" {
|
||||
rel = "INVESTOR"
|
||||
}
|
||||
in = append(in, captablesvc.StakeholderInput{
|
||||
in = append(in, captable.StakeholderInput{
|
||||
Name: h.Name, Email: h.Email, StakeholderType: st,
|
||||
CurrentRelationship: rel, InstitutionName: h.InstitutionName,
|
||||
})
|
||||
}
|
||||
return captablesvc.AddStakeholders(ctx, org, in)
|
||||
return captable.AddStakeholders(ctx, org, in)
|
||||
}
|
||||
|
||||
// RecordRound maps a company fundraising round to the captable contract.
|
||||
func (captableAdapter) RecordRound(ctx context.Context, org string, r RoundInput) (string, error) {
|
||||
return captablesvc.RecordRound(ctx, org, captablesvc.RoundInput{
|
||||
return captable.RecordRound(ctx, org, captable.RoundInput{
|
||||
Name: r.Name, RoundType: r.RoundType, TargetAmount: r.TargetAmount,
|
||||
PreMoneyValuation: r.PreMoneyValuation, PricePerShare: r.PricePerShare,
|
||||
ShareClassID: r.ShareClassID,
|
||||
@@ -130,6 +130,6 @@ func (captableUpgrader) MarkCompany(ctx context.Context, f *Formation) error {
|
||||
if name == "" {
|
||||
name = f.Org
|
||||
}
|
||||
return captablesvc.SetIncorporation(ctx, f.Org, name,
|
||||
return captable.SetIncorporation(ctx, f.Org, name,
|
||||
f.Structure.incorporationType(), "US", string(f.Jurisdiction))
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func getJSON(t *testing.T, s *cloud.Service[state], path string) map[string]any
|
||||
// column reads (items[].server/name/connectionState), with the fleet's app count,
|
||||
// and NO cluster credential.
|
||||
func TestDashClustersEndpoint(t *testing.T) {
|
||||
s := fakeSvc(
|
||||
s := fakeService(
|
||||
appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1),
|
||||
appCR("App", "hanzo", "iam", "u2", "ghcr.io/hanzoai/iam", "v1", "Running", 1, 1),
|
||||
)
|
||||
@@ -86,7 +86,7 @@ func TestDashClustersEndpoint(t *testing.T) {
|
||||
// TestDashProjectsEndpoint_Synthesizes: with no AppProject CRD served, /projects
|
||||
// synthesizes the distinct App-CR project set (default always present).
|
||||
func TestDashProjectsEndpoint_Synthesizes(t *testing.T) {
|
||||
s := fakeSvc(appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1))
|
||||
s := fakeService(appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1))
|
||||
body := getJSON(t, s, "/v1/deploy/projects")
|
||||
|
||||
items, ok := body["items"].([]any)
|
||||
@@ -107,7 +107,7 @@ func TestDashProjectsEndpoint_Synthesizes(t *testing.T) {
|
||||
// TestDashProjectsEndpoint_PrefersRealCRs: when real AppProject CRs are served,
|
||||
// /projects lists THOSE (not synthesized ones) and surfaces only intended fields.
|
||||
func TestDashProjectsEndpoint_PrefersRealCRs(t *testing.T) {
|
||||
s := fakeSvc(
|
||||
s := fakeService(
|
||||
appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1),
|
||||
appProjectCR("team-a", "https://git.hanzo.ai/team-a/*"),
|
||||
)
|
||||
@@ -136,7 +136,7 @@ func TestDashProjectsEndpoint_PrefersRealCRs(t *testing.T) {
|
||||
// the SuperAdmin claim (fail-closed, no fleet/cluster data to an anonymous caller).
|
||||
func TestNewRoutesRequireAdmin(t *testing.T) {
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
routes(app, fakeSvc())
|
||||
routes(app, fakeService())
|
||||
for _, path := range []string{"/v1/deploy/clusters", "/v1/deploy/projects", "/v1/deploy/stream/applications"} {
|
||||
// EventSource sends Accept: text/event-stream + Sec-Fetch-Dest: empty, so a
|
||||
// non-admin gets a 403 (not the browser-document redirect) — assert both.
|
||||
@@ -202,7 +202,7 @@ func TestStreamFailsClosedWithoutCluster(t *testing.T) {
|
||||
// the argo Application shape (status.health/sync), projected identically to
|
||||
// dashAppList.
|
||||
func TestStreamBurstEmitsAddedPerApp(t *testing.T) {
|
||||
s := fakeSvc(
|
||||
s := fakeService(
|
||||
appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1),
|
||||
appCR("App", "hanzo", "iam", "u2", "ghcr.io/hanzoai/iam", "v1", "Running", 1, 1),
|
||||
)
|
||||
@@ -246,7 +246,7 @@ func TestStreamBurstEmitsAddedPerApp(t *testing.T) {
|
||||
func TestStreamBurstZeroAppsNoPanic(t *testing.T) {
|
||||
var buf bytes.Buffer
|
||||
w := bufio.NewWriter(&buf)
|
||||
if ok := streamAppBurst(fakeSvc(), superScope(), context.Background(), w); !ok {
|
||||
if ok := streamAppBurst(fakeService(), superScope(), context.Background(), w); !ok {
|
||||
t.Fatal("streamAppBurst on zero apps returned false, want true")
|
||||
}
|
||||
_ = w.Flush()
|
||||
@@ -260,7 +260,7 @@ func TestStreamBurstZeroAppsNoPanic(t *testing.T) {
|
||||
// return directly (the keep-alive interval is irrelevant), so this needs no global
|
||||
// tuning and cannot race a concurrent stream.
|
||||
func TestStreamHonorsContextCancel(t *testing.T) {
|
||||
s := fakeSvc(appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1))
|
||||
s := fakeService(appCR("App", "hanzo", "cloud", "u1", "ghcr.io/hanzoai/cloud", "v1", "Running", 1, 1))
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
// /v1/deploy/ui route without wrapping it in guard() breaks this test.
|
||||
func TestDeployRoutesRequireAdmin(t *testing.T) {
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
routes(app, fakeSvc()) // the COMPLETE surface: native + engine + dashboard
|
||||
routes(app, fakeService()) // the COMPLETE surface: native + engine + dashboard
|
||||
|
||||
guarded := []struct{ method, path string }{
|
||||
// engine
|
||||
@@ -109,7 +109,7 @@ func TestDeployRoutesRequireAdmin(t *testing.T) {
|
||||
// anonymous answer carries the sign-in URL and NOTHING that identifies anyone.
|
||||
func TestUserInfoIsPublicBootstrap(t *testing.T) {
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
routes(app, fakeSvc())
|
||||
routes(app, fakeService())
|
||||
|
||||
resp, err := app.Fiber().Test(httptest.NewRequest("GET", "/v1/deploy/session/userinfo", nil))
|
||||
if err != nil {
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
|
||||
// ── fixtures ────────────────────────────────────────────────────────────────
|
||||
|
||||
func fakeSvc(objs ...runtime.Object) *cloud.Service[state] {
|
||||
func fakeService(objs ...runtime.Object) *cloud.Service[state] {
|
||||
scheme := runtime.NewScheme()
|
||||
dyn := dynamicfake.NewSimpleDynamicClientWithCustomListKinds(scheme, map[schema.GroupVersionResource]string{
|
||||
appsCRGVR: "AppList",
|
||||
@@ -235,7 +235,7 @@ func TestObserveApplication(t *testing.T) {
|
||||
// ── CR resolution ────────────────────────────────────────────────────────────
|
||||
|
||||
func TestGetAppCR(t *testing.T) {
|
||||
s := fakeSvc(appCR("App", "hanzo", "iam", "u-app", "ghcr.io/hanzoai/iam", "v2.0.0", "Running", 1, 1))
|
||||
s := fakeService(appCR("App", "hanzo", "iam", "u-app", "ghcr.io/hanzoai/iam", "v2.0.0", "Running", 1, 1))
|
||||
obj, gvr, err := getAppCR(s, context.Background(), "hanzo", "iam")
|
||||
if err != nil {
|
||||
t.Fatalf("getAppCR: %v", err)
|
||||
@@ -253,7 +253,7 @@ func TestGetAppCR(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestListAppCRs(t *testing.T) {
|
||||
s := fakeSvc(
|
||||
s := fakeService(
|
||||
appCR("App", "hanzo", "iam", "u1", "r", "v2.0.0", "Running", 1, 1),
|
||||
appCR("App", "hanzo", "cloud", "u3", "r", "v1.799.0", "Running", 1, 1),
|
||||
)
|
||||
@@ -284,7 +284,7 @@ func TestBuildTreeOwnership(t *testing.T) {
|
||||
dep := deployment("hanzo", "iam", "d1", "u1", "ghcr.io/hanzoai/iam:v1.0.0", 1, 1) // owned by CR uid u1
|
||||
svc := coreService("hanzo", "iam", "u1") // owned by CR uid u1
|
||||
p := pod("hanzo", "iam-abc", "ghcr.io/hanzoai/iam:v1.0.0", map[string]any{"app.kubernetes.io/instance": "iam"})
|
||||
s := fakeSvc(cr, dep, svc, p)
|
||||
s := fakeService(cr, dep, svc, p)
|
||||
|
||||
nodes := buildTree(s, context.Background(), "hanzo", "iam", cr)
|
||||
kinds := map[string]bool{}
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestWantsDocument(t *testing.T) {
|
||||
// navigation gets bounced to sign-in. Neither is ever served the data.
|
||||
func TestGuardRefusesNonAdmin(t *testing.T) {
|
||||
app := zip.New(zip.Config{Logger: luxlog.New("test")})
|
||||
routes(app, fakeSvc())
|
||||
routes(app, fakeService())
|
||||
|
||||
// API call (no Accept, the shape every API client and the existing e2e sends).
|
||||
resp := do(t, app, httptest.NewRequest("GET", "/v1/deploy/applications", nil))
|
||||
@@ -367,7 +367,7 @@ func TestSessionMaxAge(t *testing.T) {
|
||||
// origin the OAuth hop refuses rather than deriving a redirect_uri from the
|
||||
// caller-controlled Host / X-Forwarded-Proto headers.
|
||||
func TestSignInFailsClosedWithoutPublicOrigin(t *testing.T) {
|
||||
svc := fakeSvc()
|
||||
svc := fakeService()
|
||||
svc.State.oauth = oauth{
|
||||
issuer: "https://iam.test", clientID: defaultClientID, adminOrg: "admin",
|
||||
http: &http.Client{Timeout: time.Second},
|
||||
@@ -611,7 +611,7 @@ func signinApp(t *testing.T, issuer string) (*zip.App, *fakeIAM) {
|
||||
t.Cleanup(srv.Close)
|
||||
issuer = srv.URL
|
||||
}
|
||||
svc := fakeSvc()
|
||||
svc := fakeService()
|
||||
svc.State.oauth = oauth{
|
||||
issuer: issuer, clientID: defaultClientID, adminOrg: "admin",
|
||||
publicURL: "https://cd.hanzo.ai", http: &http.Client{Timeout: 5 * time.Second},
|
||||
|
||||
@@ -169,7 +169,7 @@ func TestResolveScope_Boundary(t *testing.T) {
|
||||
// twoTenantFleet is a fleet with apps for two tenants (in their tenant-<org> namespaces)
|
||||
// plus a system app in the platform "hanzo" namespace.
|
||||
func twoTenantFleet() *cloud.Service[state] {
|
||||
return fakeSvc(
|
||||
return fakeService(
|
||||
orgAppCR("tenant-acme", "acme-web", "acme", "storefront"),
|
||||
orgAppCR("tenant-acme", "acme-api", "acme", "storefront"),
|
||||
orgAppCR("tenant-bravo", "bravo-web", "bravo", "site"),
|
||||
@@ -330,7 +330,7 @@ func TestProjectApp_UnlabeledFallsIntoDefault(t *testing.T) {
|
||||
// NEVER surfaces the unscoped cluster-wide AppProject list (that path is SuperAdmin-only) —
|
||||
// even when real AppProject CRs are served — and always contains 'default'.
|
||||
func TestDashProjects_OrgNeverSeesCrossOrgAppProjects(t *testing.T) {
|
||||
s := fakeSvc(
|
||||
s := fakeService(
|
||||
orgAppCR("tenant-acme", "acme-web", "acme", "storefront"),
|
||||
appProjectCR("team-secret", "https://git.hanzo.ai/team-secret/*"), // a cross-org real AppProject CR
|
||||
)
|
||||
|
||||
@@ -100,7 +100,7 @@ func (z *mockZones) EnsureZone(_ context.Context, _, _ string) ([]string, error)
|
||||
|
||||
// --- helpers ---------------------------------------------------------------------
|
||||
|
||||
func newSvc(reg *mockReg, bill *mockBill, zones *mockZones) *Service {
|
||||
func newService(reg *mockReg, bill *mockBill, zones *mockZones) *Service {
|
||||
return NewService(reg, bill, zones, NewMemStore(), Config{
|
||||
Markup: Markup{Multiplier: 1.15, MinMarginCents: 300},
|
||||
Nameservers: []string{"ns1.hanzo.ai", "ns2.hanzo.ai"},
|
||||
@@ -133,7 +133,7 @@ func TestAvailabilityPricesWithMarkup(t *testing.T) {
|
||||
reg := &mockReg{configured: true, avail: map[string]namecom.SearchResult{
|
||||
"acme.ai": {DomainName: "acme.ai", Purchasable: true, PurchasePrice: 55.99, RenewalPrice: 55.99, TLD: "ai"},
|
||||
}}
|
||||
svc := newSvc(reg, &mockBill{balance: -1}, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
svc := newService(reg, &mockBill{balance: -1}, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
qs, err := svc.Availability(context.Background(), "acme.ai")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -153,7 +153,7 @@ func TestRegisterHappyPath_BillsAndPointsNameservers(t *testing.T) {
|
||||
}}
|
||||
bill := &mockBill{balance: 100000} // $1000
|
||||
zones := &mockZones{ns: []string{"ns1.hanzo.ai", "ns2.hanzo.ai"}}
|
||||
svc := newSvc(reg, bill, zones)
|
||||
svc := newService(reg, bill, zones)
|
||||
|
||||
res, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil)
|
||||
if err != nil {
|
||||
@@ -189,7 +189,7 @@ func TestRegisterRefusedWhenInsufficientBalance_NoRegistrarCall(t *testing.T) {
|
||||
"acme.ai": {DomainName: "acme.ai", Purchasable: true, PurchasePrice: 55.99},
|
||||
}}
|
||||
bill := &mockBill{balance: 100} // $1.00 — nowhere near $64.39
|
||||
svc := newSvc(reg, bill, &mockZones{})
|
||||
svc := newService(reg, bill, &mockZones{})
|
||||
|
||||
_, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil)
|
||||
if !errors.Is(err, ErrInsufficientFunds) {
|
||||
@@ -208,7 +208,7 @@ func TestRegisterRegistrarFailure_NoCharge(t *testing.T) {
|
||||
"acme.ai": {DomainName: "acme.ai", Purchasable: true, PurchasePrice: 55.99},
|
||||
}}
|
||||
bill := &mockBill{balance: 100000}
|
||||
svc := newSvc(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
svc := newService(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
|
||||
_, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil)
|
||||
if err == nil {
|
||||
@@ -224,7 +224,7 @@ func TestRegisterUnavailable(t *testing.T) {
|
||||
reg := &mockReg{configured: true, avail: map[string]namecom.SearchResult{
|
||||
"taken.ai": {DomainName: "taken.ai", Purchasable: false},
|
||||
}}
|
||||
svc := newSvc(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
svc := newService(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
_, err := svc.Register(context.Background(), "acme", "taken.ai", 1, nil)
|
||||
if !errors.Is(err, ErrUnavailable) {
|
||||
t.Fatalf("want ErrUnavailable, got %v", err)
|
||||
@@ -236,7 +236,7 @@ func TestRegisterAlreadyOwned(t *testing.T) {
|
||||
"acme.ai": {DomainName: "acme.ai", Purchasable: true, PurchasePrice: 55.99},
|
||||
}}
|
||||
bill := &mockBill{balance: 100000}
|
||||
svc := newSvc(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
svc := newService(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
if _, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -252,7 +252,7 @@ func TestRegisterFallsBackToConfigNameserversWhenZoneFails(t *testing.T) {
|
||||
}}
|
||||
bill := &mockBill{balance: 100000}
|
||||
zones := &mockZones{err: errors.New("dns down")}
|
||||
svc := newSvc(reg, bill, zones)
|
||||
svc := newService(reg, bill, zones)
|
||||
|
||||
if _, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -269,7 +269,7 @@ func TestRenewOwnedDomainBills(t *testing.T) {
|
||||
"acme.ai": {DomainName: "acme.ai", Purchasable: true, PurchasePrice: 55.99, RenewalPrice: 55.99},
|
||||
}}
|
||||
bill := &mockBill{balance: 100000}
|
||||
svc := newSvc(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
svc := newService(reg, bill, &mockZones{ns: []string{"ns1.hanzo.ai"}})
|
||||
if _, err := svc.Register(context.Background(), "acme", "acme.ai", 1, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -291,7 +291,7 @@ func TestRenewOwnedDomainBills(t *testing.T) {
|
||||
|
||||
func TestRenewNotOwned(t *testing.T) {
|
||||
reg := &mockReg{configured: true}
|
||||
svc := newSvc(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
svc := newService(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
_, err := svc.Renew(context.Background(), "acme", "nope.ai", 1)
|
||||
if !errors.Is(err, ErrNotOwned) {
|
||||
t.Fatalf("want ErrNotOwned, got %v", err)
|
||||
@@ -300,7 +300,7 @@ func TestRenewNotOwned(t *testing.T) {
|
||||
|
||||
func TestNotConfigured(t *testing.T) {
|
||||
reg := &mockReg{configured: false}
|
||||
svc := newSvc(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
svc := newService(reg, &mockBill{balance: -1}, &mockZones{})
|
||||
if _, err := svc.Availability(context.Background(), "acme.ai"); !errors.Is(err, ErrNotConfigured) {
|
||||
t.Fatalf("want ErrNotConfigured, got %v", err)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package execsvc exposes the Code Interpreter ("Run Code") surface on the
|
||||
// Package exec exposes the Code Interpreter ("Run Code") surface on the
|
||||
// unified cloud-api /v1 plane, per HIP-0106.
|
||||
//
|
||||
// hanzo.chat (LibreChat fork) drives its execute_code agent tool against a
|
||||
@@ -76,7 +76,7 @@ func newProxy(rawURL string) (http.Handler, error) {
|
||||
return nil, err
|
||||
}
|
||||
if target.Scheme == "" || target.Host == "" {
|
||||
return nil, fmt.Errorf("execsvc: CODE_EXEC_UPSTREAM must be an absolute URL, got %q", rawURL)
|
||||
return nil, fmt.Errorf("exec: CODE_EXEC_UPSTREAM must be an absolute URL, got %q", rawURL)
|
||||
}
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
base := proxy.Director
|
||||
@@ -123,11 +123,11 @@ func writeErr(w http.ResponseWriter, status int, msg string) {
|
||||
// node process with the shared service key, so we enforce that key here.
|
||||
func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
if app == nil {
|
||||
return fmt.Errorf("execsvc.Mount: nil zip.App")
|
||||
return fmt.Errorf("exec.Mount: nil zip.App")
|
||||
}
|
||||
logger := deps.Logger
|
||||
if logger == nil {
|
||||
return fmt.Errorf("execsvc.Mount: nil deps.Logger")
|
||||
return fmt.Errorf("exec.Mount: nil deps.Logger")
|
||||
}
|
||||
logger = logger.New("subsystem", "exec")
|
||||
|
||||
|
||||
@@ -85,10 +85,10 @@ func (s *sandbox) start(t *testing.T) string {
|
||||
}
|
||||
func (s *sandbox) ran() int32 { return atomic.LoadInt32(&s.calls) }
|
||||
|
||||
// newBilledSvc builds a functions service with a store, an exec client pointed at
|
||||
// newBilledService builds a functions service with a store, an exec client pointed at
|
||||
// execUpstream (empty ⇒ unconfigured), and a metering client pointed at
|
||||
// commerceURL (default org "hanzo"; empty ⇒ !Enabled()).
|
||||
func newBilledSvc(t *testing.T, commerceURL, execUpstream string) *cloud.Service[state] {
|
||||
func newBilledService(t *testing.T, commerceURL, execUpstream string) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
log := luxlog.New("module", "fnbilltest")
|
||||
m, err := metering.New(metering.Config{BaseURL: commerceURL, Token: "svc-token", Org: "hanzo"})
|
||||
@@ -141,7 +141,7 @@ func fireInvoke(t *testing.T, s *cloud.Service[state], org, name string) *http.R
|
||||
func TestInvoke_RefusesUnfundedOrg(t *testing.T) {
|
||||
sb := &sandbox{}
|
||||
bs := &billServer{available: 0}
|
||||
s := newBilledSvc(t, bs.start(t), sb.start(t))
|
||||
s := newBilledService(t, bs.start(t), sb.start(t))
|
||||
seedFn(t, s, "acme", "resize")
|
||||
|
||||
resp := fireInvoke(t, s, "acme", "resize")
|
||||
@@ -162,7 +162,7 @@ func TestInvoke_RefusesUnfundedOrg(t *testing.T) {
|
||||
func TestInvoke_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
sb := &sandbox{}
|
||||
bs := &billServer{available: 100000}
|
||||
s := newBilledSvc(t, bs.start(t), sb.start(t))
|
||||
s := newBilledService(t, bs.start(t), sb.start(t))
|
||||
seedFn(t, s, "acme", "resize")
|
||||
|
||||
resp := fireInvoke(t, s, "acme", "resize")
|
||||
@@ -207,7 +207,7 @@ func TestInvoke_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
func TestInvoke_TransportFailureNotBilled(t *testing.T) {
|
||||
bs := &billServer{available: 100000}
|
||||
// execUpstream points at a dead address so run() returns a transport error.
|
||||
s := newBilledSvc(t, bs.start(t), "http://127.0.0.1:1")
|
||||
s := newBilledService(t, bs.start(t), "http://127.0.0.1:1")
|
||||
seedFn(t, s, "acme", "resize")
|
||||
|
||||
resp := fireInvoke(t, s, "acme", "resize")
|
||||
@@ -227,7 +227,7 @@ func TestInvoke_FreeFeeUngated(t *testing.T) {
|
||||
t.Setenv("CLOUD_FUNCTION_FEE_CENTS", "0")
|
||||
sb := &sandbox{}
|
||||
bs := &billServer{available: 0}
|
||||
s := newBilledSvc(t, bs.start(t), sb.start(t))
|
||||
s := newBilledService(t, bs.start(t), sb.start(t))
|
||||
seedFn(t, s, "acme", "resize")
|
||||
|
||||
resp := fireInvoke(t, s, "acme", "resize")
|
||||
@@ -248,7 +248,7 @@ func TestInvoke_FreeFeeUngated(t *testing.T) {
|
||||
// nothing is billed.
|
||||
func TestInvoke_BillingUnconfiguredNoop(t *testing.T) {
|
||||
sb := &sandbox{}
|
||||
s := newBilledSvc(t, "", sb.start(t)) // empty commerce URL ⇒ !Enabled()
|
||||
s := newBilledService(t, "", sb.start(t)) // empty commerce URL ⇒ !Enabled()
|
||||
seedFn(t, s, "acme", "resize")
|
||||
|
||||
resp := fireInvoke(t, s, "acme", "resize")
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Package iamsvc folds Hanzo IAM into the unified hanzoai/cloud binary as an
|
||||
// Package iam folds Hanzo IAM into the unified hanzoai/cloud binary as an
|
||||
// in-process subsystem (HIP-0106) — the LAST binary-consolidation piece:
|
||||
// "one Go binary (hanzoai/cloud) embeds IAM + KMS + o11y".
|
||||
//
|
||||
|
||||
@@ -30,7 +30,7 @@ func testStore(t *testing.T) *Store {
|
||||
return st
|
||||
}
|
||||
|
||||
func testSvc(t *testing.T) *cloud.Service[state] {
|
||||
func testService(t *testing.T) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
return &cloud.Service[state]{
|
||||
Base: cloud.NewBase(cloud.Deps{Logger: luxlog.NewNoOpLogger()}, "ingress"),
|
||||
@@ -216,7 +216,7 @@ func TestTLSHostPolicy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestReloadBuildsTLSHostSet(t *testing.T) {
|
||||
s := testSvc(t)
|
||||
s := testService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// A TLS route + an org-level extraHost.
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/hanzoai/cloud"
|
||||
)
|
||||
|
||||
// testSvc builds a bare cloud.Service[state] with a fixed 32-byte signing key — enough to exercise
|
||||
// testService builds a bare cloud.Service[state] with a fixed 32-byte signing key — enough to exercise
|
||||
// sign/verify in isolation (no store/KMS/HTTP).
|
||||
func testSvc() *cloud.Service[state] {
|
||||
func testService() *cloud.Service[state] {
|
||||
key := make([]byte, minStateKeyLen)
|
||||
for i := range key {
|
||||
key[i] = byte(i + 1)
|
||||
@@ -20,7 +20,7 @@ func testSvc() *cloud.Service[state] {
|
||||
}
|
||||
|
||||
func TestStateSignVerifyHappy(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
tok, err := sign(s, "acme", "slack", "nonce-1")
|
||||
if err != nil {
|
||||
t.Fatalf("sign: %v", err)
|
||||
@@ -35,7 +35,7 @@ func TestStateSignVerifyHappy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStateTamperFails(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
tok, _ := sign(s, "acme", "slack", "n")
|
||||
// Flip a byte in the payload half (before the dot). The MAC no longer matches.
|
||||
dot := strings.IndexByte(tok, '.')
|
||||
@@ -61,7 +61,7 @@ func TestStateTamperFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStateExpiredFails(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
old := stateTTL
|
||||
stateTTL = -time.Minute // sign with an already-past exp
|
||||
tok, _ := sign(s, "acme", "slack", "n")
|
||||
@@ -72,7 +72,7 @@ func TestStateExpiredFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStateWrongProviderFails(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
tok, _ := sign(s, "acme", "slack", "n")
|
||||
if _, err := verify(s, tok, "github"); err == nil {
|
||||
t.Fatal("state signed for slack must fail verify for github")
|
||||
@@ -80,10 +80,10 @@ func TestStateWrongProviderFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStateWrongKeyFails(t *testing.T) {
|
||||
signer := testSvc()
|
||||
signer := testService()
|
||||
tok, _ := sign(signer, "acme", "slack", "n")
|
||||
|
||||
other := testSvc()
|
||||
other := testService()
|
||||
other.State.stateKey = make([]byte, minStateKeyLen) // all-zero: a different key
|
||||
if _, err := verify(other, tok, "slack"); err == nil {
|
||||
t.Fatal("a token signed under a different key must fail verify")
|
||||
@@ -91,7 +91,7 @@ func TestStateWrongKeyFails(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestStateMalformedFails(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
for _, bad := range []string{"", ".", "abc", "abc.", ".abc", "onlyonepart"} {
|
||||
if _, err := verify(s, bad, "slack"); err == nil {
|
||||
t.Fatalf("malformed token %q must fail verify", bad)
|
||||
@@ -104,7 +104,7 @@ func TestStateMalformedFails(t *testing.T) {
|
||||
// an empty payload half all fail — the MAC is bound to the exact payload substring
|
||||
// before the FIRST dot, and a base64url payload/MAC never itself contains a '.'.
|
||||
func TestStateDotInjectionRejected(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
tok, _ := sign(s, "acme", "slack", "n")
|
||||
dot := strings.IndexByte(tok, '.')
|
||||
payloadB64, macB64 := tok[:dot], tok[dot+1:]
|
||||
@@ -128,7 +128,7 @@ func TestStateDotInjectionRejected(t *testing.T) {
|
||||
// carrying an attacker-chosen MAC, is rejected at the constant-time MAC gate and
|
||||
// never reaches (and never panics in) json.Unmarshal.
|
||||
func TestStateMACCheckedBeforeParse(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
garbage := base64.URLEncoding.EncodeToString([]byte("this-is-not-json-{{{"))
|
||||
forgedMAC := base64.URLEncoding.EncodeToString(make([]byte, 32)) // all-zero MAC
|
||||
if _, err := verify(s, garbage+"."+forgedMAC, "slack"); err == nil {
|
||||
@@ -141,7 +141,7 @@ func TestStateMACCheckedBeforeParse(t *testing.T) {
|
||||
// org would smuggle path structure — so a callback can never fold a hostile org
|
||||
// into the KMS/store key, independent of the connect-boundary check.
|
||||
func TestStateBadOrgRejected(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
for _, org := range []string{"bad/org", "..", "a b", "org\x00", ""} {
|
||||
tok, err := sign(s, org, "slack", "n")
|
||||
if err != nil {
|
||||
@@ -156,7 +156,7 @@ func TestStateBadOrgRejected(t *testing.T) {
|
||||
// TestStateOverlongRejected proves an oversized token is rejected before any
|
||||
// base64 work — capping the allocation a forged callback can force.
|
||||
func TestStateOverlongRejected(t *testing.T) {
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
huge := strings.Repeat("A", maxStateLen+1) + "." + strings.Repeat("B", 64)
|
||||
if _, err := verify(s, huge, "slack"); err == nil {
|
||||
t.Fatal("token longer than maxStateLen must fail verify")
|
||||
|
||||
@@ -60,7 +60,7 @@ func (b *billDouble) lastDebit() (string, []byte) {
|
||||
return b.usageOrg, b.usageBody
|
||||
}
|
||||
|
||||
func newBilledMLSvc(t *testing.T, commerceURL string) *cloud.Service[state] {
|
||||
func newBilledMLService(t *testing.T, commerceURL string) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
log := luxlog.New("module", "mlbilltest")
|
||||
m, err := metering.New(metering.Config{BaseURL: commerceURL, Token: "svc-token", Org: "hanzo"})
|
||||
@@ -101,7 +101,7 @@ func postTrainJob(t *testing.T, s *cloud.Service[state], org string) *http.Respo
|
||||
// billed. This closes the free-GPU hole.
|
||||
func TestComputeCreate_RefusesUnfundedOrg(t *testing.T) {
|
||||
bd := &billDouble{available: 0}
|
||||
s := newBilledMLSvc(t, bd.start(t))
|
||||
s := newBilledMLService(t, bd.start(t))
|
||||
|
||||
resp := postTrainJob(t, s, "acme")
|
||||
if resp.StatusCode != http.StatusPaymentRequired {
|
||||
@@ -121,7 +121,7 @@ func TestComputeCreate_RefusesUnfundedOrg(t *testing.T) {
|
||||
// hanzo) is debited the compute fee under provider "compute".
|
||||
func TestComputeCreate_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
bd := &billDouble{available: 100000}
|
||||
s := newBilledMLSvc(t, bd.start(t))
|
||||
s := newBilledMLService(t, bd.start(t))
|
||||
|
||||
resp := postTrainJob(t, s, "acme")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
|
||||
@@ -24,10 +24,10 @@ func seedGitApp(t *testing.T, s *cloud.Service[state], org, slug, repoURL, branc
|
||||
return a
|
||||
}
|
||||
|
||||
// pushSvc mounts a Service over a ready fake cluster (no HTTP routes needed —
|
||||
// pushService mounts a Service over a ready fake cluster (no HTTP routes needed —
|
||||
// buildFromPush is called directly) and trusts the embedded-git apex as a build
|
||||
// source, exactly as platform.Mount does from deps.Domain.
|
||||
func pushSvc(t *testing.T) *cloud.Service[state] {
|
||||
func pushService(t *testing.T) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
_, s := mountSvcK8s(t, fakeK8s())
|
||||
prev := selfGitHost
|
||||
@@ -40,7 +40,7 @@ func pushSvc(t *testing.T) *cloud.Service[state] {
|
||||
// "building" and a "building" deployment for the pushed commit is recorded.
|
||||
func TestBuildFromPush_LaunchesMatchingApp(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s := pushSvc(t)
|
||||
s := pushService(t)
|
||||
const clone = "https://git.hanzo.ai/v1/git/acme/site.git"
|
||||
a := seedGitApp(t, s, "acme", "site", "https://git.hanzo.ai/v1/git/acme/site", "main")
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestBuildFromPush_LaunchesMatchingApp(t *testing.T) {
|
||||
// A push to a branch no app tracks is a no-op: no deployment, no error.
|
||||
func TestBuildFromPush_NoMatchIsNoop(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s := pushSvc(t)
|
||||
s := pushService(t)
|
||||
a := seedGitApp(t, s, "acme", "site", "https://git.hanzo.ai/v1/git/acme/site", "main")
|
||||
|
||||
// Right repo, wrong branch.
|
||||
@@ -92,7 +92,7 @@ func TestBuildFromPush_NoMatchIsNoop(t *testing.T) {
|
||||
// An image-source app matching the repo URL is never built by a push (git only).
|
||||
func TestBuildFromPush_IgnoresImageApp(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
s := pushSvc(t)
|
||||
s := pushService(t)
|
||||
img := Application{
|
||||
ID: "app_acme_api", Org: "acme", ProjectID: "default", Slug: "api", Name: "api",
|
||||
Source: "image", RepoURL: "https://git.hanzo.ai/v1/git/acme/api", ImageRepo: "ghcr.io/hanzoai/api", ImageTag: "1",
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func testSvc() *cloud.Service[state] {
|
||||
func testService() *cloud.Service[state] {
|
||||
return &cloud.Service[state]{Base: cloud.Base{Log: luxlog.New("test")}}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ func TestComputeReleaseVersion(t *testing.T) {
|
||||
defer srv.Close()
|
||||
defer swapAPIBase(srv.URL)()
|
||||
|
||||
got, err := computeReleaseVersion(testSvc(), context.Background(), releaseRepoSlug)
|
||||
got, err := computeReleaseVersion(testService(), context.Background(), releaseRepoSlug)
|
||||
if err != nil {
|
||||
t.Fatalf("computeReleaseVersion: %v", err)
|
||||
}
|
||||
@@ -232,7 +232,7 @@ func TestTagRelease_RefPath(t *testing.T) {
|
||||
defer swapAPIBase(srv.URL)()
|
||||
|
||||
sha := "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"
|
||||
if err := tagRelease(testSvc(), context.Background(), releaseRepoSlug, sha, "v1.786.44"); err != nil {
|
||||
if err := tagRelease(testService(), context.Background(), releaseRepoSlug, sha, "v1.786.44"); err != nil {
|
||||
t.Fatalf("tagRelease: %v", err)
|
||||
}
|
||||
if gotBody["ref"] != "refs/tags/v1.786.44" || gotBody["sha"] != sha {
|
||||
@@ -260,7 +260,7 @@ func TestNotifyUniverse_Payload(t *testing.T) {
|
||||
defer swapAPIBase(srv.URL)()
|
||||
|
||||
img := "ghcr.io/hanzoai/cloud:v1.786.44"
|
||||
if err := notifyUniverse(testSvc(), context.Background(), img, "deadbeef"); err != nil {
|
||||
if err := notifyUniverse(testService(), context.Background(), img, "deadbeef"); err != nil {
|
||||
t.Fatalf("notifyUniverse: %v", err)
|
||||
}
|
||||
if got["event_type"] != "image-update" {
|
||||
@@ -277,7 +277,7 @@ func TestNotifyUniverse_Payload(t *testing.T) {
|
||||
func TestReleaseSeams_FailClosedWithoutTokens(t *testing.T) {
|
||||
t.Setenv("GH_PAT", "")
|
||||
t.Setenv("UNIVERSE_DISPATCH_TOKEN", "")
|
||||
s := testSvc()
|
||||
s := testService()
|
||||
if err := tagRelease(s, context.Background(), releaseRepoSlug, "sha", "v1.0.0"); err == nil {
|
||||
t.Fatal("tagRelease with no GH_PAT: want fail-closed error")
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package pluginsvc is the runtime plugin loader for the unified cloud binary.
|
||||
// Package plugin is the runtime plugin loader for the unified cloud binary.
|
||||
//
|
||||
// cloud is a thin host: its native Go subsystems are compiled in, but
|
||||
// everything else mounts at RUNTIME from a manifest — no cloud rebuild to add
|
||||
@@ -13,7 +13,7 @@
|
||||
// client when available; until then proxying uses plain HTTP. Either
|
||||
// way cloud never recompiles to point at a service.
|
||||
//
|
||||
// The manifest path comes from CLOUD_PLUGINS (a JSON file); if unset, pluginsvc
|
||||
// The manifest path comes from CLOUD_PLUGINS (a JSON file); if unset, plugin
|
||||
// mounts nothing. Adding a service = edit the manifest + drop a .wasm or
|
||||
// redeploy the standalone — cloud is unchanged unless its own core changes.
|
||||
package plugin
|
||||
@@ -62,7 +62,7 @@ type Manifest struct {
|
||||
//
|
||||
// The proxy kind dials its target through an http.RoundTripper chosen by
|
||||
// Plugin.Via. "http" is built in; "zap" (and any future transport) is
|
||||
// registered here by its client package, so pluginsvc has no hard dependency
|
||||
// registered here by its client package, so plugin has no hard dependency
|
||||
// on the ZAP wire code and works today over HTTP.
|
||||
|
||||
var (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package pricingsvc mounts the @hanzo/pricing service into the unified cloud
|
||||
// Package pricing mounts the @hanzo/pricing service into the unified cloud
|
||||
// binary under /v1/pricing/* (+ the /v1/models, /v1/gpu, /v1/tools aliases),
|
||||
// per HIP-0106.
|
||||
//
|
||||
@@ -99,7 +99,7 @@ func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
// must stay hidden across restarts. A non-persistent (in-memory) overlay would
|
||||
// silently re-expose hidden models on the next pod start — a fail-OPEN
|
||||
// degradation of a security control. So an empty DataDir is a hard boot error
|
||||
// (prod sets CLOUD_DATA_DIR), never a silent downgrade. provisioningsvc already
|
||||
// (prod sets CLOUD_DATA_DIR), never a silent downgrade. provisioning already
|
||||
// requires DataDir, so the unified binary always provides one.
|
||||
if deps.DataDir == "" {
|
||||
return fmt.Errorf("pricing.Mount: empty DataDir — the catalog enablement overlay requires a persistent data dir (set CLOUD_DATA_DIR); refusing to boot with a non-persistent overlay that would re-expose admin-hidden models on restart")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package productsvc exposes the read-only Search and Vector product surfaces
|
||||
// Package product exposes the read-only Search and Vector product surfaces
|
||||
// the Hanzo console panels call at api.cloud.hanzo.ai, per HIP-0106.
|
||||
//
|
||||
// The console's Search/Indexes and Vector panels call
|
||||
|
||||
@@ -98,7 +98,7 @@ func TestStore_InstanceColumnRoundTrips(t *testing.T) {
|
||||
// and returns a postgres:// DSN authenticated as the "admin" superuser.
|
||||
func TestDedicated_SQLEngineAssemblesDSN(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreate(t, s, "sql", "acme", "orders")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
@@ -147,7 +147,7 @@ func TestDedicated_SQLEngineAssemblesDSN(t *testing.T) {
|
||||
// redis://default:… DSN — never an "admin" user that would fail AUTH.
|
||||
func TestDedicated_KVEngineAssemblesDSN(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreate(t, s, "kv", "acme", "sessions")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
@@ -202,7 +202,7 @@ func TestDedicated_KVEngineAssemblesDSN(t *testing.T) {
|
||||
// the same instance MERGES (never clobbers the first).
|
||||
func TestDedicated_InstanceBindingInjectsURL(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
r1 := postCreateInstance(t, s, "datastore", "acme", "warehouse", "commerce")
|
||||
if r1.StatusCode != http.StatusCreated {
|
||||
@@ -239,7 +239,7 @@ func TestDedicated_InstanceBindingInjectsURL(t *testing.T) {
|
||||
// instance touches no addons Secret — the pre-binding behavior is unchanged.
|
||||
func TestDedicated_NotInstanceBoundSkipsInjection(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
if resp := postCreate(t, s, "datastore", "acme", "warehouse"); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("create = %d", resp.StatusCode)
|
||||
@@ -254,7 +254,7 @@ func TestDedicated_NotInstanceBoundSkipsInjection(t *testing.T) {
|
||||
// key is gone afterward.
|
||||
func TestDedicated_DropRemovesURLBeforeTeardown(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
if resp := postCreateInstance(t, s, "datastore", "acme", "warehouse", "commerce"); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("create = %d", resp.StatusCode)
|
||||
@@ -296,7 +296,7 @@ func TestDedicated_DropRemovesURLBeforeTeardown(t *testing.T) {
|
||||
func TestDedicated_InjectFailureRollsBack(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
orch.patchErr = context.DeadlineExceeded // make PatchAddonSecret fail
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreateInstance(t, s, "datastore", "acme", "warehouse", "commerce")
|
||||
if resp.StatusCode != http.StatusBadGateway {
|
||||
@@ -326,7 +326,7 @@ func TestDedicated_InjectPartialWriteRollsBackOrphanKey(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
orch.patchErr = context.DeadlineExceeded
|
||||
orch.patchErrAfterWrite = true // the key lands, THEN the call reports failure
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreateInstance(t, s, "datastore", "acme", "warehouse", "commerce")
|
||||
if resp.StatusCode != http.StatusBadGateway {
|
||||
|
||||
@@ -65,9 +65,9 @@ func (b *billServer) lastDebit() (string, []byte) {
|
||||
return b.usageOrg, b.usageBody
|
||||
}
|
||||
|
||||
// newBilledSvc builds a provisioning Service with a mock provisioner and a real
|
||||
// newBilledService builds a provisioning Service with a mock provisioner and a real
|
||||
// metering client pointed at commerceURL (default org "hanzo").
|
||||
func newBilledSvc(t *testing.T, commerceURL string, kinds ...string) (*cloud.Service[state], *mockProv) {
|
||||
func newBilledService(t *testing.T, commerceURL string, kinds ...string) (*cloud.Service[state], *mockProv) {
|
||||
t.Helper()
|
||||
t.Setenv("CLOUD_KMS_NODES", "")
|
||||
t.Setenv("CLOUD_KMS_PASSPHRASE", "")
|
||||
@@ -113,7 +113,7 @@ func postCreate(t *testing.T, s *cloud.Service[state], kind, org, name string) *
|
||||
// runs before the backend). No free provisioning.
|
||||
func TestCreate_RefusesUnfundedOrg(t *testing.T) {
|
||||
bs := &billServer{available: 0}
|
||||
s, mp := newBilledSvc(t, bs.start(t), "vector")
|
||||
s, mp := newBilledService(t, bs.start(t), "vector")
|
||||
|
||||
resp := postCreate(t, s, "vector", "acme", "orders")
|
||||
if resp.StatusCode != http.StatusPaymentRequired {
|
||||
@@ -136,7 +136,7 @@ func TestCreate_RefusesUnfundedOrg(t *testing.T) {
|
||||
// client default hanzo) is debited the provision fee.
|
||||
func TestCreate_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
bs := &billServer{available: 100000}
|
||||
s, mp := newBilledSvc(t, bs.start(t), "vector")
|
||||
s, mp := newBilledService(t, bs.start(t), "vector")
|
||||
|
||||
resp := postCreate(t, s, "vector", "acme", "orders")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
@@ -171,7 +171,7 @@ func TestCreate_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
func TestCreate_FreeKindUngated(t *testing.T) {
|
||||
t.Setenv("CLOUD_PROVISION_FEE_CENTS_VECTOR", "0")
|
||||
bs := &billServer{available: 0}
|
||||
s, mp := newBilledSvc(t, bs.start(t), "vector")
|
||||
s, mp := newBilledService(t, bs.start(t), "vector")
|
||||
|
||||
resp := postCreate(t, s, "vector", "acme", "orders")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
@@ -191,7 +191,7 @@ func TestCreate_FreeKindUngated(t *testing.T) {
|
||||
// Billing unconfigured (no commerce URL) → the gate is a no-op: provisioning
|
||||
// works and nothing is billed (an unconfigured deployment is never blocked).
|
||||
func TestCreate_BillingUnconfiguredNoop(t *testing.T) {
|
||||
s, mp := newBilledSvc(t, "", "vector") // empty commerce URL => !Enabled()
|
||||
s, mp := newBilledService(t, "", "vector") // empty commerce URL => !Enabled()
|
||||
resp := postCreate(t, s, "vector", "acme", "orders")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
@@ -109,7 +109,7 @@ func (f *fakeOrch) DeletePVC(_ context.Context, ns, name string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDedicatedSvc(t *testing.T, orch orchestrator) *cloud.Service[state] {
|
||||
func newDedicatedService(t *testing.T, orch orchestrator) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
t.Setenv("CLOUD_KMS_NODES", "")
|
||||
t.Setenv("CLOUD_KMS_PASSPHRASE", "")
|
||||
@@ -152,7 +152,7 @@ func doReq(t *testing.T, h zip.Handler, method, route, path, org, bodyStr string
|
||||
// dimension, and returns a DSN pointing at the instance's OWN in-cluster Service.
|
||||
func TestDedicated_CreateLaunchesInstance(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreate(t, s, "datastore", "acme", "analytics")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
@@ -231,7 +231,7 @@ func TestDedicated_CreateLaunchesInstance(t *testing.T) {
|
||||
// namespaces — no shared backend, no cross-tenant reachability.
|
||||
func TestDedicated_TwoOrgsSeparateInstances(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
if resp := postCreate(t, s, "docdb", "acme", "events"); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("acme create = %d", resp.StatusCode)
|
||||
@@ -261,7 +261,7 @@ func TestDedicated_TwoOrgsSeparateInstances(t *testing.T) {
|
||||
// operator reports the instance StatefulSet Running — never before.
|
||||
func TestDedicated_ReadyReconcile(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
if resp := postCreate(t, s, "datastore", "acme", "warehouse"); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("create = %d", resp.StatusCode)
|
||||
@@ -292,7 +292,7 @@ func TestDedicated_ReadyReconcile(t *testing.T) {
|
||||
// TestDedicated_DropTearsDownInstance: delete removes the CR + admin Secret + row.
|
||||
func TestDedicated_DropTearsDownInstance(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
if resp := postCreate(t, s, "docdb", "acme", "sessions"); resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("create = %d", resp.StatusCode)
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
// magic — no WiredTiger mongod datadir, no Postgres PG_VERSION).
|
||||
func TestDedicated_DocdbIsFerretOnSQL(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
|
||||
resp := postCreate(t, s, "docdb", "acme", "events")
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
|
||||
@@ -51,7 +51,7 @@ func TestUnavailableKinds_EmptyAfterDedicated(t *testing.T) {
|
||||
func TestCreate_DedicatedFailsClosedWithoutCluster(t *testing.T) {
|
||||
for _, kind := range []string{"datastore", "docdb", "sql", "kv"} {
|
||||
t.Run(kind, func(t *testing.T) {
|
||||
s, _ := newTestSvc(t) // no orch, no bill
|
||||
s, _ := newTestService(t) // no orch, no bill
|
||||
resp := postCreate(t, s, kind, "acme", "warehouse")
|
||||
if resp.StatusCode != http.StatusServiceUnavailable {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
luxlog "github.com/luxfi/log"
|
||||
)
|
||||
|
||||
func liveSvc(t *testing.T) *cloud.Service[state] {
|
||||
func liveService(t *testing.T) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
t.Setenv("CLOUD_KMS_NODES", "")
|
||||
t.Setenv("CLOUD_KMS_PASSPHRASE", "")
|
||||
@@ -83,7 +83,7 @@ func dropLive(t *testing.T, s *cloud.Service[state], kind, org, name string) {
|
||||
}
|
||||
|
||||
func TestLive_DedicatedProvisioning(t *testing.T) {
|
||||
s := liveSvc(t)
|
||||
s := liveService(t)
|
||||
ctx := context.Background()
|
||||
|
||||
// Two orgs, each gets its OWN datastore instance in its OWN namespace.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package provisioningsvc is the Hanzo Cloud provisioning control plane. It
|
||||
// Package provisioning is the Hanzo Cloud provisioning control plane. It
|
||||
// turns "create a database" into a real logical resource inside an
|
||||
// already-live, shared product backend, per the unified /v1 binary (HIP-0106).
|
||||
//
|
||||
@@ -647,7 +647,7 @@ func genID() (string, error) {
|
||||
var mounted *cloud.Service[state]
|
||||
|
||||
// Shutdown closes the provisioning metadata store. Idempotent. Mirrors the
|
||||
// plansvc Shutdown contract so the serve layer can release subsystem resources
|
||||
// plan Shutdown contract so the serve layer can release subsystem resources
|
||||
// uniformly.
|
||||
func Shutdown(context.Context) error {
|
||||
if mounted == nil || mounted.State.store == nil {
|
||||
|
||||
@@ -53,9 +53,9 @@ func (m *mockProv) Create(_ context.Context, _, _, pw string) (string, string, i
|
||||
|
||||
func (m *mockProv) Drop(_ context.Context, _, _ string) error { m.dropped++; return nil }
|
||||
|
||||
// newTestSvc builds a provisioning Service with a temp store, KMS-degraded secrets (no env),
|
||||
// newTestService builds a provisioning Service with a temp store, KMS-degraded secrets (no env),
|
||||
// and a mock provisioner under each given kind.
|
||||
func newTestSvc(t *testing.T, kinds ...string) (*cloud.Service[state], *mockProv) {
|
||||
func newTestService(t *testing.T, kinds ...string) (*cloud.Service[state], *mockProv) {
|
||||
t.Helper()
|
||||
// Force KMS degrade so secret persistence is hermetic and never dials.
|
||||
t.Setenv("CLOUD_KMS_NODES", "")
|
||||
@@ -394,7 +394,7 @@ func TestGenToken(t *testing.T) {
|
||||
// TestCreateOrgGate: a non-admin POST with no X-Org-Id is refused 403 before
|
||||
// anything is provisioned.
|
||||
func TestCreateOrgGate(t *testing.T) {
|
||||
s, mp := newTestSvc(t, "sql")
|
||||
s, mp := newTestService(t, "sql")
|
||||
app := zip.New(zip.Config{DisableStartupMessage: true})
|
||||
app.Post("/v1/sql", create(s, "sql"))
|
||||
|
||||
@@ -421,7 +421,7 @@ func TestCreateOrgGate(t *testing.T) {
|
||||
// gate this forged request would allocate a DB in the victim's namespace and
|
||||
// return its connection string + generated password, or (on DELETE) destroy it.
|
||||
func TestForgedOrgWithoutPrincipalRefused(t *testing.T) {
|
||||
s, mp := newTestSvc(t, "sql")
|
||||
s, mp := newTestService(t, "sql")
|
||||
app := zip.New(zip.Config{DisableStartupMessage: true})
|
||||
app.Post("/v1/sql", create(s, "sql"))
|
||||
app.Delete("/v1/sql/:name", drop(s, "sql"))
|
||||
@@ -464,7 +464,7 @@ func TestForgedOrgWithoutPrincipalRefused(t *testing.T) {
|
||||
// the only kinds that mint a per-resource credential.
|
||||
func TestCreateKMSDegradePersistsNoPlaintext(t *testing.T) {
|
||||
orch := newFakeOrch()
|
||||
s := newDedicatedSvc(t, orch)
|
||||
s := newDedicatedService(t, orch)
|
||||
if s.State.sec.Enabled() {
|
||||
t.Fatal("precondition: KMS must be degraded for this test")
|
||||
}
|
||||
|
||||
@@ -69,10 +69,10 @@ func (b *billServer) lastDebit() (string, []byte) {
|
||||
return b.usageOrg, b.usageBody
|
||||
}
|
||||
|
||||
// newBilledSvc builds an s3 Service with S3 admin credentials present (so guard does
|
||||
// newBilledService builds an s3 Service with S3 admin credentials present (so guard does
|
||||
// not 503 on Configured()) and a metering client pointed at commerceURL (default
|
||||
// org "hanzo"; empty ⇒ !Enabled()).
|
||||
func newBilledSvc(t *testing.T, commerceURL string) *cloud.Service[state] {
|
||||
func newBilledService(t *testing.T, commerceURL string) *cloud.Service[state] {
|
||||
t.Helper()
|
||||
t.Setenv("S3_ADMIN_ACCESS_KEY", "AKIATEST")
|
||||
t.Setenv("S3_ADMIN_SECRET_KEY", "secrettest")
|
||||
@@ -119,7 +119,7 @@ func callGuard(t *testing.T, s *cloud.Service[state], org string, hErr error) (s
|
||||
// touched), nothing is debited.
|
||||
func TestGuard_RefusesUnfundedOrg(t *testing.T) {
|
||||
bs := &billServer{available: 0}
|
||||
s := newBilledSvc(t, bs.start(t))
|
||||
s := newBilledService(t, bs.start(t))
|
||||
|
||||
status, ran := callGuard(t, s, "acme", nil)
|
||||
if status != http.StatusPaymentRequired {
|
||||
@@ -137,7 +137,7 @@ func TestGuard_RefusesUnfundedOrg(t *testing.T) {
|
||||
// debited once with product "s3" / unit "op".
|
||||
func TestGuard_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
bs := &billServer{available: 100000}
|
||||
s := newBilledSvc(t, bs.start(t))
|
||||
s := newBilledService(t, bs.start(t))
|
||||
|
||||
status, ran := callGuard(t, s, "acme", nil)
|
||||
if status != http.StatusNoContent {
|
||||
@@ -178,7 +178,7 @@ func TestGuard_AllowsAndDebitsCallerOrg(t *testing.T) {
|
||||
// mirrors the edge gate ("do not bill failed work").
|
||||
func TestGuard_HandlerFailureNotBilled(t *testing.T) {
|
||||
bs := &billServer{available: 100000}
|
||||
s := newBilledSvc(t, bs.start(t))
|
||||
s := newBilledService(t, bs.start(t))
|
||||
|
||||
status, ran := callGuard(t, s, "acme", zip.Errorf(http.StatusBadGateway, "s3 down"))
|
||||
if status != http.StatusBadGateway {
|
||||
@@ -198,7 +198,7 @@ func TestGuard_HandlerFailureNotBilled(t *testing.T) {
|
||||
func TestGuard_FreeFeeUngated(t *testing.T) {
|
||||
t.Setenv("CLOUD_S3_FEE_CENTS", "0")
|
||||
bs := &billServer{available: 0}
|
||||
s := newBilledSvc(t, bs.start(t))
|
||||
s := newBilledService(t, bs.start(t))
|
||||
|
||||
status, ran := callGuard(t, s, "acme", nil)
|
||||
if status != http.StatusNoContent {
|
||||
@@ -216,7 +216,7 @@ func TestGuard_FreeFeeUngated(t *testing.T) {
|
||||
// Billing unconfigured (no commerce URL) → the gate is a no-op: the op runs and
|
||||
// nothing is billed.
|
||||
func TestGuard_BillingUnconfiguredNoop(t *testing.T) {
|
||||
s := newBilledSvc(t, "") // empty commerce URL ⇒ !Enabled()
|
||||
s := newBilledService(t, "") // empty commerce URL ⇒ !Enabled()
|
||||
|
||||
status, ran := callGuard(t, s, "acme", nil)
|
||||
if status != http.StatusNoContent {
|
||||
@@ -231,7 +231,7 @@ func TestGuard_BillingUnconfiguredNoop(t *testing.T) {
|
||||
// gate — the tenant boundary precedes billing.
|
||||
func TestGuard_NoPrincipalRefused(t *testing.T) {
|
||||
bs := &billServer{available: 100000}
|
||||
s := newBilledSvc(t, bs.start(t))
|
||||
s := newBilledService(t, bs.start(t))
|
||||
|
||||
status, ran := callGuard(t, s, "", nil)
|
||||
if status != http.StatusForbidden {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package syncsvc is the universal sync service (/v1/sync): cloud↔cloud data
|
||||
// Package sync is the universal sync service (/v1/sync): cloud↔cloud data
|
||||
// sync between connected platforms, expressed as Syncs the engine runs. Git
|
||||
// (GitHub/GitLab ⇆ native Hanzo Git) is the FIRST provider; storage, db, and other
|
||||
// kinds are new providers at their own kind with nothing in the engine to change.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package tasksvc mounts the Hanzo Tasks HTTP + UI surface natively onto the
|
||||
// Package tasks mounts the Hanzo Tasks HTTP + UI surface natively onto the
|
||||
// unified cloud binary per HIP-0106 — the follow-up named in cloud's durable.go
|
||||
// ("consolidating that surface into cloud"). Tasks is the durable
|
||||
// workflow/activity engine (event-sourced, exactly-once, crash-recovering)
|
||||
@@ -46,10 +46,10 @@ import (
|
||||
// creates NO engine — the ONE engine lives in cloud.EmbeddedTasks (durable.go).
|
||||
func Mount(app *zip.App, deps cloud.Deps) error {
|
||||
if app == nil {
|
||||
return fmt.Errorf("tasksvc.Mount: nil zip.App")
|
||||
return fmt.Errorf("tasks.Mount: nil zip.App")
|
||||
}
|
||||
if deps.Logger == nil {
|
||||
return fmt.Errorf("tasksvc.Mount: nil deps.Logger")
|
||||
return fmt.Errorf("tasks.Mount: nil deps.Logger")
|
||||
}
|
||||
|
||||
h := zip.AdaptNetHTTP(&surface{})
|
||||
|
||||
@@ -146,7 +146,7 @@ func TestSafeCustody(t *testing.T) {
|
||||
if !safeCl.configured() {
|
||||
t.Fatal("safe client should be configured with base + secret")
|
||||
}
|
||||
s, app := newSvc(t, map[Kind]Custody{KindSafe: safeCustody{mpc: mpcCl, safe: safeCl}}, KindSafe)
|
||||
s, app := newService(t, map[Kind]Custody{KindSafe: safeCustody{mpc: mpcCl, safe: safeCl}}, KindSafe)
|
||||
|
||||
acct := mkAccount(t, app, "acme")
|
||||
w := mkWallet(t, app, "acme", acct, "safe", "warm", "eip155:36963")
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestValidNarrowingRejectsInjection(t *testing.T) {
|
||||
|
||||
func TestScopedWalletSealsAtScopedRef(t *testing.T) {
|
||||
k, _ := testKMS(t)
|
||||
_, app := newSvc(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
_, app := newService(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
|
||||
acct := mkAccount(t, app, "acme")
|
||||
|
||||
@@ -120,7 +120,7 @@ func TestScopedWalletSealsAtScopedRef(t *testing.T) {
|
||||
|
||||
func TestScopeLookupPath(t *testing.T) {
|
||||
k, _ := testKMS(t)
|
||||
s, app := newSvc(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
s, app := newService(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
ctx := context.Background()
|
||||
|
||||
// Provision four wallets in org "acme" spanning distinct scopes, plus one in a
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
|
||||
// ── harness ──────────────────────────────────────────────────────────────────
|
||||
|
||||
// newSvc builds a wallets service over a fresh temp store with the given custody set,
|
||||
// newService builds a wallets service over a fresh temp store with the given custody set,
|
||||
// installs it as the process singleton, and mounts the routes on a fresh app.
|
||||
func newSvc(t *testing.T, custody map[Kind]Custody, def Kind) (*cloud.Service[state], *zip.App) {
|
||||
func newService(t *testing.T, custody map[Kind]Custody, def Kind) (*cloud.Service[state], *zip.App) {
|
||||
t.Helper()
|
||||
st, err := openStore(filepath.Join(t.TempDir(), "wallets.db"))
|
||||
if err != nil {
|
||||
@@ -154,7 +154,7 @@ func isHexAddr(s string) bool {
|
||||
|
||||
func TestKMSSingleSigEndToEnd(t *testing.T) {
|
||||
k, dir := testKMS(t)
|
||||
_, app := newSvc(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
_, app := newService(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
|
||||
acct := mkAccount(t, app, "acme")
|
||||
w := mkWallet(t, app, "acme", acct, "kms", "hot", "eip155:1")
|
||||
@@ -242,7 +242,7 @@ func assertSealedAtRest(t *testing.T, dir, plaintextHex string) {
|
||||
|
||||
func TestPerTenantIsolation(t *testing.T) {
|
||||
k, _ := testKMS(t)
|
||||
_, app := newSvc(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
_, app := newService(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
|
||||
// Org A owns a wallet.
|
||||
acctA := mkAccount(t, app, "orga")
|
||||
@@ -289,7 +289,7 @@ func TestPerTenantIsolation(t *testing.T) {
|
||||
func TestCustodySeamSelectsBackend(t *testing.T) {
|
||||
k, _ := testKMS(t)
|
||||
// Only KMS is configured — mpc/treasury must fail closed.
|
||||
s, app := newSvc(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
s, app := newService(t, map[Kind]Custody{KindKMS: kmsCustody{kms: k}}, KindKMS)
|
||||
|
||||
// Resolver: kms resolves; mpc/treasury fail closed; unknown is a distinct error.
|
||||
if _, err := custodyFor(s, KindKMS); err != nil {
|
||||
@@ -371,7 +371,7 @@ func TestMPCPathWiredCompiles(t *testing.T) {
|
||||
if !client.configured() {
|
||||
t.Fatal("mpc client should be configured with a node + key")
|
||||
}
|
||||
s, app := newSvc(t, map[Kind]Custody{KindMPC: mpcCustody{http: client}}, KindMPC)
|
||||
s, app := newService(t, map[Kind]Custody{KindMPC: mpcCustody{http: client}}, KindMPC)
|
||||
|
||||
acct := mkAccount(t, app, "acme")
|
||||
w := mkWallet(t, app, "acme", acct, "mpc", "warm", "eip155:1")
|
||||
|
||||
Reference in New Issue
Block a user