package cmd import ( "net/http" "net/http/httptest" "strings" "testing" ) // orgTestServer spins up an httptest server that records the path, method, and // the two credential headers of the request it receives, then returns the // supplied body. Used to assert the org-lifecycle verbs hit the CP ADMIN // surface with the CP-admin bearer (and NOT the tenant org key/org-id header). type capturedReq struct { method, path, auth, orgID string hadAuth bool } func orgTestServer(t *testing.T, body string) (*httptest.Server, *capturedReq) { t.Helper() got := &capturedReq{} srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got.method = r.Method got.path = r.URL.Path got.auth = r.Header.Get("Authorization") got.orgID = r.Header.Get("X-Molecule-Org-Id") _, got.hadAuth = r.Header["Authorization"] w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(body)) })) t.Cleanup(srv.Close) return srv, got } // TestOrgList_TargetsAdminSurfaceWithCPAdminBearer asserts `org list` hits the // control-plane ADMIN route (/api/v1/admin/orgs) authenticated with the // CP-admin bearer (MOLECULE_CP_ADMIN_TOKEN) — and critically does NOT leak the // tenant Org API Key (MOLECULE_API_KEY) or send the X-Molecule-Org-Id header. func TestOrgList_TargetsAdminSurfaceWithCPAdminBearer(t *testing.T) { srv, got := orgTestServer(t, `{"limit":100,"offset":0,"orgs":[{"slug":"acme","name":"Acme","plan":"pro","instance_status":"running","member_count":3}]}`) // Tenant org key is present in the env but must NOT be used for CP calls. t.Setenv("MOLECULE_API_KEY", "org-key-SECRET") t.Setenv("MOLECULE_ORG_ID", "org_should_not_leak") t.Setenv("MOLECULE_CP_ADMIN_TOKEN", "cp-admin-bearer-123") t.Setenv("MOLECULE_CP_URL", srv.URL) origFmt := outputFormat defer func() { outputFormat = origFmt }() outputFormat = "json" if err := runOrgList(nil, nil); err != nil { t.Fatalf("runOrgList: %v", err) } if got.method != "GET" || got.path != "/api/v1/admin/orgs" { t.Errorf("target = %s %s, want GET /api/v1/admin/orgs", got.method, got.path) } if got.auth != "Bearer cp-admin-bearer-123" { t.Errorf("Authorization = %q, want CP-admin bearer", got.auth) } if strings.Contains(got.auth, "org-key-SECRET") { t.Errorf("tenant Org API Key leaked to the control plane: %q", got.auth) } if got.orgID != "" { t.Errorf("X-Molecule-Org-Id should not be sent to the CP admin surface, got %q", got.orgID) } } // TestOrgCreate_TargetsAdminSurfaceWithOwnerAndCPAdminBearer asserts `org // create` POSTs to /api/v1/admin/orgs with the CP-admin bearer and the // required owner_user_id, never the tenant org key. func TestOrgCreate_TargetsAdminSurfaceWithOwnerAndCPAdminBearer(t *testing.T) { srv, got := orgTestServer(t, `{"id":"o1","slug":"acme","name":"Acme","plan":"free","created_at":"now"}`) t.Setenv("MOLECULE_API_KEY", "org-key-SECRET") t.Setenv("MOLECULE_ORG_ID", "org_should_not_leak") t.Setenv("MOLECULE_CP_ADMIN_TOKEN", "cp-admin-bearer-123") t.Setenv("MOLECULE_CP_URL", srv.URL) origFmt := outputFormat defer func() { outputFormat = origFmt }() outputFormat = "json" orgCreateFlags.slug = "acme" orgCreateFlags.name = "Acme" orgCreateFlags.ownerUserID = "user_123" orgCreateFlags.template = "" defer func() { orgCreateFlags.slug, orgCreateFlags.name, orgCreateFlags.ownerUserID = "", "", "" }() if err := runOrgCreate(nil, nil); err != nil { t.Fatalf("runOrgCreate: %v", err) } if got.method != "POST" || got.path != "/api/v1/admin/orgs" { t.Errorf("target = %s %s, want POST /api/v1/admin/orgs", got.method, got.path) } if got.auth != "Bearer cp-admin-bearer-123" { t.Errorf("Authorization = %q, want CP-admin bearer", got.auth) } if got.orgID != "" { t.Errorf("X-Molecule-Org-Id should not be sent to the CP admin surface, got %q", got.orgID) } } // TestOrgCreate_RequiresOwnerUserID confirms the admin create path fails fast // (no network call) when --owner-user-id is missing. func TestOrgCreate_RequiresOwnerUserID(t *testing.T) { t.Setenv("MOLECULE_CP_ADMIN_TOKEN", "cp-admin-bearer-123") orgCreateFlags.slug = "acme" orgCreateFlags.name = "Acme" orgCreateFlags.ownerUserID = "" orgCreateFlags.template = "" defer func() { orgCreateFlags.slug, orgCreateFlags.name = "", "" }() err := runOrgCreate(nil, nil) if err == nil { t.Fatal("expected error when --owner-user-id is missing") } if !strings.Contains(err.Error(), "owner-user-id") { t.Errorf("error = %q, want it to mention owner-user-id", err.Error()) } } // TestOrgVerbs_FailFastWithoutCPAdminToken is the WRONG-CREDENTIAL path: when // only the tenant Org API Key is set (no MOLECULE_CP_ADMIN_TOKEN), the // CP-targeting verbs must fail fast with a clear message — NOT silently send // the org key to the control plane and 401. func TestOrgVerbs_FailFastWithoutCPAdminToken(t *testing.T) { // Tenant key present, CP-admin token deliberately absent. t.Setenv("MOLECULE_API_KEY", "org-key-SECRET") t.Setenv("MOLECULE_ORG_ID", "org_abc") t.Setenv("MOLECULE_CP_ADMIN_TOKEN", "") // Point CP at an unreachable host so any accidental network call is a // hard, obvious failure rather than a hang. t.Setenv("MOLECULE_CP_URL", "http://127.0.0.1:0") // list if err := runOrgList(nil, nil); err == nil { t.Error("org list should fail fast without MOLECULE_CP_ADMIN_TOKEN") } else if !strings.Contains(err.Error(), "MOLECULE_CP_ADMIN_TOKEN") { t.Errorf("org list error = %q, want it to name MOLECULE_CP_ADMIN_TOKEN", err.Error()) } // create orgCreateFlags.slug = "acme" orgCreateFlags.name = "Acme" orgCreateFlags.ownerUserID = "user_123" orgCreateFlags.template = "" defer func() { orgCreateFlags.slug, orgCreateFlags.name, orgCreateFlags.ownerUserID = "", "", "" }() if err := runOrgCreate(nil, nil); err == nil { t.Error("org create should fail fast without MOLECULE_CP_ADMIN_TOKEN") } else if !strings.Contains(err.Error(), "MOLECULE_CP_ADMIN_TOKEN") { t.Errorf("org create error = %q, want it to name MOLECULE_CP_ADMIN_TOKEN", err.Error()) } } // TestOrgGetExport_FailFast confirms get/export do not pretend to work over // token auth: they return a clear unavailable error instead of 401'ing the // session-only CP routes. func TestOrgGetExport_FailFast(t *testing.T) { t.Setenv("MOLECULE_CP_ADMIN_TOKEN", "cp-admin-bearer-123") // even WITH the admin token if err := runOrgGet(nil, []string{"acme"}); err == nil { t.Error("org get should fail fast (session-only on the CP)") } else if !strings.Contains(err.Error(), "session") { t.Errorf("org get error = %q, want it to explain the session gate", err.Error()) } if err := runOrgExport(nil, []string{"acme"}); err == nil { t.Error("org export should fail fast (session-only on the CP)") } else if !strings.Contains(err.Error(), "session") { t.Errorf("org export error = %q, want it to explain the session gate", err.Error()) } }