From 722808afc488d2eef40747f066dee73294ab95dd Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sat, 10 Oct 2026 04:27:19 +0200 Subject: [PATCH 1/3] feat(insurance): add read-only archera-comparison command Explicit, default-off comparison of an Archera commitment plan using pkg/insurance. Key from ARCHERA_API_KEY only (no flag); org and plan IDs from env or flags. Table or JSON output via cmd.OutOrStdout, exact decimal money, unknown never rendered as 0, vendor strings sanitized, disclosures verbatim. Never purchases and makes no request unless run. Closes #2156 Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 6 + README.md | 2 +- cmd/archera_comparison.go | 113 +++++++++++ cmd/archera_comparison_test.go | 359 +++++++++++++++++++++++++++++++++ cmd/archera_render.go | 338 +++++++++++++++++++++++++++++++ docs/cli/archera-comparison.md | 51 +++++ 6 files changed, 868 insertions(+), 1 deletion(-) create mode 100644 cmd/archera_comparison.go create mode 100644 cmd/archera_comparison_test.go create mode 100644 cmd/archera_render.go create mode 100644 docs/cli/archera-comparison.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e94c2e2b3..87996f8b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ## [Unreleased] +### Added + +- `archera-comparison` command: a read-only, default-off comparison of an + Archera commitment plan (table or JSON). See `docs/cli/archera-comparison.md` + (#2156). + ### Fixed - Reject NaN and infinite values for `--coverage`, `--target-coverage`, diff --git a/README.md b/README.md index aeecf78a7..0e6249f97 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ make build `make build` creates `./cudly` from `./cmd`. The build does not deploy or configure a cloud account. -Read the [CLI reference](docs/cli/README.md) for commands. See the guides for [cloud setup](docs/cli/cloud-setup.md), [filtering](docs/cli/filtering.md), and [purchase safety](docs/cli/purchase-safety.md). +Read the [CLI reference](docs/cli/README.md) for commands. See the guides for [cloud setup](docs/cli/cloud-setup.md), [filtering](docs/cli/filtering.md), and [purchase safety](docs/cli/purchase-safety.md). `archera-comparison` is an optional, default-off, read-only Archera plan comparison: see [Archera comparison](docs/cli/archera-comparison.md). ## Common workflows diff --git a/cmd/archera_comparison.go b/cmd/archera_comparison.go new file mode 100644 index 000000000..92df84415 --- /dev/null +++ b/cmd/archera_comparison.go @@ -0,0 +1,113 @@ +package main + +import ( + "errors" + "fmt" + "net/http" + "os" + "strings" + + "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" + "github.com/spf13/cobra" +) + +// Archera settings come from the environment, read only inside RunE. The API +// key has no flag by design. +const ( + archeraEnvAPIKey = "ARCHERA_API_KEY" // #nosec G101 -- environment variable name, not a credential + archeraEnvOrgID = "ARCHERA_ORG_ID" + archeraEnvPlanID = "ARCHERA_PLAN_ID" +) + +// archeraHTTPClient is nil in production, which selects the hardened default +// client inside pkg/insurance. Tests replace it with a fixed-origin transport. +// It never holds the key; the key lives only in RunE locals. +var archeraHTTPClient *http.Client + +var archeraOpts struct { + OrgID string + PlanID string + Format string +} + +var archeraComparisonCmd = &cobra.Command{ + Use: "archera-comparison", + Short: "Show a read-only Archera commitment plan comparison", + Long: `Fetch and print the comparison for one Archera commitment plan. + +This command is read-only: it never purchases, binds or submits anything, and +it is independent of --purchase. It makes no Archera request unless it is run. + +Configuration (default off; there is no flag for the API key): + ARCHERA_API_KEY Archera API key (environment only) + ARCHERA_ORG_ID Archera organization UUID (or --org-id) + ARCHERA_PLAN_ID Archera commitment plan UUID (or --plan-id)`, + Args: cobra.NoArgs, + SilenceUsage: true, + SilenceErrors: true, + RunE: runArcheraComparison, +} + +func init() { + rootCmd.AddCommand(archeraComparisonCmd) + archeraComparisonCmd.Flags().StringVar(&archeraOpts.OrgID, "org-id", "", "Archera organization UUID (default: $"+archeraEnvOrgID+")") + archeraComparisonCmd.Flags().StringVar(&archeraOpts.PlanID, "plan-id", "", "Archera commitment plan UUID (default: $"+archeraEnvPlanID+")") + archeraComparisonCmd.Flags().StringVar(&archeraOpts.Format, "format", "table", "Output format: table or json") +} + +// archeraSetting returns the flag value, else the environment value, trimmed. +func archeraSetting(flagValue, envName string) string { + if v := strings.TrimSpace(flagValue); v != "" { + return v + } + return strings.TrimSpace(os.Getenv(envName)) +} + +func runArcheraComparison(cmd *cobra.Command, _ []string) error { + format := archeraOpts.Format + if format != "table" && format != "json" { + return errors.New("--format must be table or json") + } + key, orgID, planID, err := archeraSettings() + if err != nil { + return err + } + + // Config and client stay local to this call; never store them in a + // package variable or struct. + client, err := insurance.NewClient(insurance.Config{APIKey: key, OrgID: orgID}, archeraHTTPClient) + if err != nil { + return err + } + comparison, err := client.Comparison(cmd.Context(), insurance.ComparisonRequest{PlanID: planID}) + if err != nil { + var httpErr *insurance.HTTPError + if errors.As(err, &httpErr) && httpErr.RetryAfter > 0 { + return fmt.Errorf("%w (retry after %s; not retried automatically)", err, httpErr.RetryAfter) + } + return err + } + dto := buildArcheraDTO(comparison) + if format == "json" { + return renderArcheraJSON(cmd.OutOrStdout(), dto) + } + return renderArcheraTable(cmd.OutOrStdout(), dto) +} + +// archeraSettings reads the key, org ID and plan ID, naming (never echoing) +// the first missing setting. The key is returned to RunE only. +func archeraSettings() (key, orgID, planID string, err error) { + key = strings.TrimSpace(os.Getenv(archeraEnvAPIKey)) + if key == "" { + return "", "", "", fmt.Errorf("%s is not set (read from the environment only)", archeraEnvAPIKey) + } + orgID = archeraSetting(archeraOpts.OrgID, archeraEnvOrgID) + if orgID == "" { + return "", "", "", fmt.Errorf("archera org ID is not set (--org-id or %s)", archeraEnvOrgID) + } + planID = archeraSetting(archeraOpts.PlanID, archeraEnvPlanID) + if planID == "" { + return "", "", "", fmt.Errorf("archera plan ID is not set (--plan-id or %s)", archeraEnvPlanID) + } + return key, orgID, planID, nil +} diff --git a/cmd/archera_comparison_test.go b/cmd/archera_comparison_test.go new file mode 100644 index 000000000..9c68162ad --- /dev/null +++ b/cmd/archera_comparison_test.go @@ -0,0 +1,359 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "math/big" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "sync" + "testing" + "time" + + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + archeraTestKey = "test-key-SYNTHETIC-0000" + archeraTestOrg = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + archeraTestPlan = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +) + +// Synthetic fixtures shaped like the documented commitment-plans schema. +const archeraFinancialsFixture = `{ + "commitment_cost": {"total": 110, "breakdown": {"cloud_provider_cost": {"total": 100}, "archera_premium": 10.125}}, + "commitment_savings": {"net": null, "gross": 4.5}, + "covered_ondemand_cost": 104.5 +}` + +func archeraOfferFixture(isCurrent bool, offerType, lease, term, payment, name string) string { + return fmt.Sprintf(`{ + "is_current": %t, + "offer_id": "11111111-1111-4111-8111-111111111111", + "offer_org_id": "public", + "offer": {"provider": "aws", "type": %q, "region": "us-east-1", "guaranteed_display_name": %s}, + "lease_menu_item_id": %s, + "selected_amount": 3, + "commitment_type": %q, + "contract_term": %s, + "payment_option": %s, + "discount_rate": 0.3, + "breakeven_days": null, + "commitment_upfront_cost": 1200, + "commitment_financials_monthly_rate": %s, + "delta_vs_current": {"monthly_net_savings": 0, "upfront_cost": 0, "discount_rate": 0, "breakeven_days": null} + }`, isCurrent, offerType, name, lease, offerType, term, payment, archeraFinancialsFixture) +} + +func archeraComparisonFixture() string { + return `{ + "current_totals": {"commitment_financials_monthly_rate": ` + archeraFinancialsFixture + `, "commitment_upfront_cost": 1200}, + "hypothetical_totals": [{ + "contract_term": null, + "payment_option": "no_upfront", + "commitment_financials_monthly_rate": {}, + "commitment_upfront_cost": 0, + "delta_vs_current": {"monthly_net_savings": 7.25, "monthly_commitment_cost": -1, "upfront_cost": -1200}, + "line_items": [{ + "line_item_id": "22222222-2222-4222-8222-222222222222", + "actual_term": "one_year_gris", "actual_payment_option": "no_upfront", + "actual_commitment_type": "aws/AmazonEC2", + "actual_term_reason": "fallback_closest_shorter" + }] + }], + "data": [{ + "line_item_id": "22222222-2222-4222-8222-222222222222", + "current": ` + archeraOfferFixture(true, "aws/AmazonEC2", "null", "null", "null", "null") + `, + "candidates": [` + + archeraOfferFixture(false, "aws/AmazonEC2", `"33333333-3333-4333-8333-333333333333"`, `"one_year_gris"`, `"no_upfront"`, `"Offer\u001b[31m RED"`) + `,` + + archeraOfferFixture(false, "aws/SomethingNew", "null", `"one_year_gris"`, `"no_upfront"`, "null") + `] + }] + }` +} + +type archeraRecorder struct { + mu sync.Mutex + requests []*http.Request + hits map[string]int +} + +// archeraTestTransport asserts the production origin before rewriting the +// request to the httptest server, so only the fixed origin is ever exercised. +type archeraTestTransport struct { + t *testing.T + target *url.URL + rec *archeraRecorder +} + +func (a *archeraTestTransport) RoundTrip(r *http.Request) (*http.Response, error) { + require.Equal(a.t, "https", r.URL.Scheme) + require.Equal(a.t, "api.archera.ai", r.URL.Host) + a.rec.mu.Lock() + a.rec.requests = append(a.rec.requests, r.Clone(context.Background())) + a.rec.mu.Unlock() + rewritten := r.Clone(r.Context()) + rewritten.URL.Scheme, rewritten.URL.Host = a.target.Scheme, a.target.Host + rewritten.Host = a.target.Host + return http.DefaultTransport.RoundTrip(rewritten) +} + +func setupArchera(t *testing.T, handler http.HandlerFunc) *archeraRecorder { + t.Helper() + rec := &archeraRecorder{hits: map[string]int{}} + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + rec.mu.Lock() + rec.hits[r.URL.Path]++ + rec.mu.Unlock() + handler(w, r) + })) + t.Cleanup(srv.Close) + target, err := url.Parse(srv.URL) + require.NoError(t, err) + archeraHTTPClient = &http.Client{Transport: &archeraTestTransport{t: t, target: target, rec: rec}} + t.Cleanup(func() { archeraHTTPClient = nil }) + return rec +} + +func okHandler(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(archeraComparisonFixture())) +} + +// resetHelpFlags clears the sticky --help flag cobra leaves set after a help run. +func resetHelpFlags() { + for _, c := range []*cobra.Command{rootCmd, archeraComparisonCmd, configureGCPCmd} { + if f := c.Flags().Lookup("help"); f != nil { + _ = f.Value.Set("false") + f.Changed = false + } + } +} + +func runArchera(t *testing.T, env map[string]string, args ...string) (stdout, stderr string, err error) { + t.Helper() + for _, k := range []string{archeraEnvAPIKey, archeraEnvOrgID, archeraEnvPlanID} { + t.Setenv(k, "") + } + for k, v := range env { + t.Setenv(k, v) + } + resetHelpFlags() + archeraOpts.OrgID, archeraOpts.PlanID, archeraOpts.Format = "", "", "table" + var out, errb bytes.Buffer + rootCmd.SetOut(&out) + rootCmd.SetErr(&errb) + rootCmd.SetArgs(append([]string{"archera-comparison"}, args...)) + t.Cleanup(func() { rootCmd.SetOut(nil); rootCmd.SetErr(nil); rootCmd.SetArgs(nil) }) + err = rootCmd.Execute() + return out.String(), errb.String(), err +} + +func fullEnv() map[string]string { + return map[string]string{archeraEnvAPIKey: archeraTestKey, archeraEnvOrgID: archeraTestOrg, archeraEnvPlanID: archeraTestPlan} +} + +func TestArcheraComparisonDefaultOffMakesNoRequest(t *testing.T) { + rec := setupArchera(t, func(http.ResponseWriter, *http.Request) { t.Error("unexpected Archera request") }) + for _, args := range [][]string{{"--help"}} { + _, _, err := runArchera(t, fullEnv(), args...) + require.NoError(t, err) + } + rootCmd.SetArgs([]string{"configure-gcp", "--help"}) + var out bytes.Buffer + rootCmd.SetOut(&out) + require.NoError(t, rootCmd.Execute()) + resetHelpFlags() + assert.Empty(t, rec.requests) +} + +func TestArcheraComparisonIncompleteConfig(t *testing.T) { + rec := setupArchera(t, func(http.ResponseWriter, *http.Request) { t.Error("unexpected request") }) + cases := []struct { + name string + env map[string]string + wantMsg string + }{ + {"no key", map[string]string{archeraEnvOrgID: archeraTestOrg, archeraEnvPlanID: archeraTestPlan}, archeraEnvAPIKey}, + {"no org", map[string]string{archeraEnvAPIKey: archeraTestKey, archeraEnvPlanID: archeraTestPlan}, "org ID"}, + {"no plan", map[string]string{archeraEnvAPIKey: archeraTestKey, archeraEnvOrgID: archeraTestOrg}, "plan ID"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out, errOut, err := runArchera(t, tc.env) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.wantMsg) + assert.NotContains(t, out+errOut+err.Error(), archeraTestKey) + }) + } + assert.Empty(t, rec.requests) +} + +func TestArcheraComparisonBadFormat(t *testing.T) { + setupArchera(t, okHandler) + _, _, err := runArchera(t, fullEnv(), "--format", "xml") + require.ErrorContains(t, err, "--format") +} + +func TestArcheraComparisonTable(t *testing.T) { + rec := setupArchera(t, okHandler) + out, errOut, err := runArchera(t, fullEnv()) + require.NoError(t, err) + + require.Len(t, rec.requests, 1) + req := rec.requests[0] + assert.Equal(t, http.MethodGet, req.Method) + assert.Equal(t, "/v1/org/"+archeraTestOrg+"/commitment-plans/"+archeraTestPlan+"/comparison", req.URL.Path) + assert.Empty(t, req.URL.RawQuery) + assert.Equal(t, archeraTestKey, req.Header.Get("x-api-key")) + + assert.Contains(t, out, archeraTitle) + assert.NotContains(t, strings.ToLower(out), "insured quote") + assert.Contains(t, out, "premium 10.125", "premium kept exact") + assert.Contains(t, out, "net savings unknown", "null money is unknown, not 0") + assert.Contains(t, out, "730-hour monthly rate") + assert.Contains(t, out, "one-time") + assert.Contains(t, out, "The API reports no currency") + assert.Contains(t, out, "reason fallback_closest_shorter") + assert.Contains(t, out, "lease attached") + assert.Contains(t, out, "Archera offer name: Offer[31m RED", "control characters stripped, text kept") + assert.NotContains(t, out, "\x1b") + assert.Contains(t, out, "Archera product support: supported (") + assert.Contains(t, out, "Archera product support: unknown") + assert.Contains(t, out, "discount rate 0.3 (0-1 basis)") + assert.Contains(t, out, common.ArcheraNonGatingDisclosure) + assert.Contains(t, out, common.ArcheraSponsorshipDisclosure) + assert.NotContains(t, out, common.ArcheraSignupURL) + assert.Contains(t, out, "not mapped onto CUDly recommendation rows") + assert.NotContains(t, out+errOut, archeraTestKey) +} + +func TestArcheraComparisonJSON(t *testing.T) { + setupArchera(t, okHandler) + out, errOut, err := runArchera(t, fullEnv(), "--format", "json") + require.NoError(t, err) + assert.Empty(t, errOut) + var doc map[string]any + dec := json.NewDecoder(strings.NewReader(out)) + require.NoError(t, dec.Decode(&doc), "stdout must be exactly one JSON document") + assert.False(t, dec.More()) + assert.Nil(t, doc["currency"]) + assert.Equal(t, true, doc["premium_included"]) + assert.Equal(t, common.ArcheraNonGatingDisclosure, doc["non_gating_disclosure"]) + assert.Equal(t, common.ArcheraSponsorshipDisclosure, doc["sponsorship_disclosure"]) + assert.NotContains(t, doc, "disclosures") + assert.NotContains(t, out, "\x1b") + assert.NotContains(t, out, `\u001b`) + assert.NotContains(t, out, archeraTestKey) + + cur := doc["current_totals"].(map[string]any)["monthly_rate_730h"].(map[string]any) + assert.Equal(t, "10.125", cur["premium"]) + assert.Nil(t, cur["net_savings"]) + rows := doc["rows"].([]any) + cand := rows[0].(map[string]any)["candidates"].([]any) + support := cand[0].(map[string]any)["archera_product_support"].(map[string]any) + assert.Equal(t, "supported", support["status"]) + assert.NotEmpty(t, support["source"]) + unknown := cand[1].(map[string]any)["archera_product_support"].(map[string]any) + assert.Equal(t, "unknown", unknown["status"]) + assert.NotContains(t, unknown, "source") +} + +func TestArcheraMoneyExact(t *testing.T) { + for in, want := range map[string]string{"0": "0", "7.25": "7.25", "10.125": "10.125", "1200": "1200", "0.00001": "0.00001"} { + r, ok := new(big.Rat).SetString(in) + require.True(t, ok) + got := archeraMoney(r) + require.NotNil(t, got) + assert.Equal(t, want, *got, in) + } + assert.Nil(t, archeraMoney(nil)) +} + +func TestArcheraSanitize(t *testing.T) { + got := archeraSanitize("a\x1bb\u009bc\nd") + assert.Equal(t, "abcd", got) + long := archeraSanitize(strings.Repeat("é", 300)) + assert.LessOrEqual(t, len(long), archeraMaxFieldSize) +} + +func TestArcheraVendorErrorsAndRedaction(t *testing.T) { + cases := []struct { + name string + status int + header map[string]string + body string + want string + }{ + {"401", 401, nil, `{"detail":"nope"}`, "HTTP 401"}, + {"403 echoing key", 403, nil, `{"detail":"bad key ` + archeraTestKey + `"}`, "HTTP 403"}, + {"429", 429, map[string]string{"Retry-After": "30"}, `{"detail":"slow down"}`, "retry after 30s"}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + rec := setupArchera(t, func(w http.ResponseWriter, _ *http.Request) { + for k, v := range tc.header { + w.Header().Set(k, v) + } + w.WriteHeader(tc.status) + _, _ = w.Write([]byte(tc.body)) + }) + out, errOut, err := runArchera(t, fullEnv()) + require.Error(t, err) + assert.Contains(t, err.Error(), tc.want) + assert.NotContains(t, out+errOut+err.Error(), archeraTestKey) + assert.Len(t, rec.requests, 1, "no automatic retry") + }) + } + t.Run("invalid uuid", func(t *testing.T) { + setupArchera(t, okHandler) + env := fullEnv() + env[archeraEnvOrgID] = "not-a-uuid" + out, errOut, err := runArchera(t, env) + require.Error(t, err) + assert.NotContains(t, out+errOut+err.Error(), archeraTestKey) + assert.NotContains(t, err.Error(), "not-a-uuid") + }) +} + +func TestArcheraRedirectNotFollowed(t *testing.T) { + rec := setupArchera(t, func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/second") { + t.Error("redirect target was requested") + return + } + http.Redirect(w, r, "/second", http.StatusFound) + }) + out, errOut, err := runArchera(t, fullEnv()) + require.Error(t, err) + assert.NotContains(t, out+errOut+err.Error(), archeraTestKey) + assert.Zero(t, rec.hits["/second"]) +} + +func TestArcheraNoKeyFlagAndStructsRedacted(t *testing.T) { + for _, name := range []string{"api-key", "key", "token", "secret", "archera-api-key"} { + assert.Nil(t, archeraComparisonCmd.Flags().Lookup(name), name) + } + check := func(label string) { + comparison, err := insurance.DecodeComparison(strings.NewReader(archeraComparisonFixture()), archeraTestOrg, archeraTestPlan, time.Unix(0, 0)) + require.NoError(t, err) + dto := buildArcheraDTO(comparison) + for _, v := range []any{archeraOpts, &archeraOpts, dto, &dto} { + for _, f := range []string{"%v", "%+v", "%#v", "%s"} { + assert.NotContains(t, fmt.Sprintf(f, v), archeraTestKey, label+" "+f) + } + } + } + setupArchera(t, okHandler) + check("before") + _, _, err := runArchera(t, fullEnv()) + require.NoError(t, err) + check("after") +} diff --git a/cmd/archera_render.go b/cmd/archera_render.go new file mode 100644 index 000000000..ece6a3839 --- /dev/null +++ b/cmd/archera_render.go @@ -0,0 +1,338 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "math/big" + "strings" + "unicode" + "unicode/utf8" + + "github.com/LeanerCloud/cloud-commitments-go/pkg/common" + "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" +) + +const ( + archeraTitle = "Archera commitment plan comparison (read-only)" + archeraMonthlyBasis = "730-hour monthly rate" + archeraUpfrontBasis = "one-time" + archeraPlanWideNote = "This comparison covers the whole Archera plan. It is not mapped onto CUDly recommendation rows or recommendation CSV output." + archeraCaveat = "A plan comparison is a hypothetical rollup, never a bindable insurance quote, and never a purchase. " + + "An insured target of 100% is a requested target subject to Archera underwriting allowances, not a guarantee. " + + "The API exposes no per-line product support or allowance verdict, so product support and allowance are shown as unknown unless Archera's documentation says otherwise." + archeraPremiumNote = "Totals include the Archera premium. The API reports no currency, so amounts are unlabelled." + archeraMaxFieldSize = 256 + archeraMaxMoneyPrec = 100 +) + +// archeraSanitize strips control characters (ESC, C1 and the rest of +// unicode.IsControl) from a vendor string and caps it at archeraMaxFieldSize +// bytes on a rune boundary, so it is safe to print to a terminal. +func archeraSanitize(s string) string { + var b strings.Builder + for _, r := range s { + if unicode.IsControl(r) { + continue + } + if b.Len()+utf8.RuneLen(r) > archeraMaxFieldSize { + break + } + b.WriteRune(r) + } + return b.String() +} + +func archeraSanitizePtr(s *string) *string { + if s == nil { + return nil + } + v := archeraSanitize(*s) + return &v +} + +// archeraMoney renders an exact decimal using the smallest precision at which +// FloatString round-trips to the same Rat. No float64 and no division. nil is +// unknown and returns nil so JSON gets null. +func archeraMoney(r *big.Rat) *string { + if r == nil { + return nil + } + for p := 0; p <= archeraMaxMoneyPrec; p++ { + s := r.FloatString(p) + if back, ok := new(big.Rat).SetString(s); ok && back.Cmp(r) == 0 { + return &s + } + } + s := r.String() + return &s +} + +type archeraFinancialsDTO struct { + CommitmentCostTotal *string `json:"commitment_cost_total"` + CloudProviderCost *string `json:"cloud_provider_cost"` + Premium *string `json:"premium"` + GrossSavings *string `json:"gross_savings"` + NetSavings *string `json:"net_savings"` + CoveredOnDemandCost *string `json:"covered_on_demand_cost"` +} + +type archeraTotalsDTO struct { + Monthly archeraFinancialsDTO `json:"monthly_rate_730h"` + UpfrontCost *string `json:"upfront_cost_one_time"` +} + +type archeraLineItemDTO struct { + LineItemID string `json:"line_item_id"` + ActualTerm *string `json:"actual_term"` + ActualPaymentOption *string `json:"actual_payment_option"` + ActualCommitmentType *string `json:"actual_commitment_type"` + Reason string `json:"reason"` +} + +type archeraHypotheticalDTO struct { + ContractTerm *string `json:"contract_term"` + PaymentOption string `json:"payment_option"` + Totals archeraTotalsDTO `json:"totals"` + DeltaMonthlyNetSavings *string `json:"delta_monthly_net_savings"` + DeltaMonthlyCommitmentCost *string `json:"delta_monthly_commitment_cost"` + DeltaUpfrontCost *string `json:"delta_upfront_cost"` + LineItems []archeraLineItemDTO `json:"line_items"` +} + +type archeraProductSupportDTO struct { + Status string `json:"status"` + Source string `json:"source,omitempty"` + Evidence string `json:"evidence,omitempty"` +} + +type archeraDeltaDTO struct { + MonthlyNetSavings *string `json:"monthly_net_savings"` + UpfrontCost *string `json:"upfront_cost"` + DiscountRate *string `json:"discount_rate"` + BreakevenDays *string `json:"breakeven_days"` +} + +type archeraOfferDTO struct { + IsCurrent bool `json:"is_current"` + OfferID string `json:"offer_id"` + CommitmentType string `json:"commitment_type"` + Provider string `json:"provider"` + Region *string `json:"region"` + ContractTerm *string `json:"contract_term"` + PaymentOption *string `json:"payment_option"` + LeaseAttached bool `json:"lease_attached"` + ArcheraOfferName *string `json:"archera_offer_name"` + DiscountRate *string `json:"discount_rate"` + BreakevenDays *string `json:"breakeven_days"` + Totals archeraTotalsDTO `json:"totals"` + DeltaVsCurrent archeraDeltaDTO `json:"delta_vs_current"` + ArcheraProductSupport archeraProductSupportDTO `json:"archera_product_support"` +} + +type archeraRowDTO struct { + LineItemID string `json:"line_item_id"` + Current archeraOfferDTO `json:"current"` + Candidates []archeraOfferDTO `json:"candidates"` +} + +// archeraComparisonDTO is the CLI's stable JSON contract. Money values are +// exact decimal strings; null means unknown, never zero. +type archeraComparisonDTO struct { + Title string `json:"title"` + PlanID string `json:"plan_id"` + FetchedAt string `json:"fetched_at"` + Currency *string `json:"currency"` + PremiumIncluded bool `json:"premium_included"` + MonthlyBasis string `json:"monthly_basis"` + UpfrontBasis string `json:"upfront_basis"` + PlanWide string `json:"plan_wide_note"` + Caveat string `json:"caveat"` + Current archeraTotalsDTO `json:"current_totals"` + Hypotheticals []archeraHypotheticalDTO `json:"hypothetical_totals"` + Rows []archeraRowDTO `json:"rows"` + NonGatingDisclosure string `json:"non_gating_disclosure"` + SponsorshipDisclosure string `json:"sponsorship_disclosure"` +} + +func archeraFinancials(f insurance.Financials) archeraFinancialsDTO { + return archeraFinancialsDTO{ + CommitmentCostTotal: archeraMoney(f.CommitmentCostTotal), + CloudProviderCost: archeraMoney(f.CloudProviderCost), + Premium: archeraMoney(f.Premium), + GrossSavings: archeraMoney(f.GrossSavings), + NetSavings: archeraMoney(f.NetSavings), + CoveredOnDemandCost: archeraMoney(f.CoveredOnDemandCost), + } +} + +func archeraTotals(t insurance.Totals) archeraTotalsDTO { + return archeraTotalsDTO{Monthly: archeraFinancials(t.Monthly), UpfrontCost: archeraMoney(t.UpfrontCost)} +} + +func archeraPayment(p *insurance.PaymentOption) *string { + if p == nil { + return nil + } + s := archeraSanitize(string(*p)) + return &s +} + +func archeraOffer(e insurance.OfferEntry) archeraOfferDTO { + support := insurance.AssessProductSupport(e.Provider, e.CommitmentType) + ps := archeraProductSupportDTO{Status: string(support.Status)} + if support.Status == insurance.ProductSupportSupported { + ps.Source, ps.Evidence = archeraSanitize(support.Source), archeraSanitize(support.Evidence) + } + return archeraOfferDTO{ + IsCurrent: e.IsCurrent, + OfferID: archeraSanitize(e.OfferID), + CommitmentType: archeraSanitize(e.CommitmentType), + Provider: archeraSanitize(string(e.Provider)), + Region: archeraSanitizePtr(e.Region), + ContractTerm: archeraSanitizePtr(e.ContractTerm), + PaymentOption: archeraPayment(e.PaymentOption), + LeaseAttached: e.LeaseBacked(), + ArcheraOfferName: archeraSanitizePtr(e.GuaranteedDisplayName), + DiscountRate: archeraMoney(e.DiscountRate), + BreakevenDays: archeraMoney(e.BreakevenDays), + Totals: archeraTotalsDTO{Monthly: archeraFinancials(e.Monthly), UpfrontCost: archeraMoney(e.UpfrontCost)}, + DeltaVsCurrent: archeraDeltaDTO{ + MonthlyNetSavings: archeraMoney(e.Delta.MonthlyNetSavings), + UpfrontCost: archeraMoney(e.Delta.UpfrontCost), + DiscountRate: archeraMoney(e.Delta.DiscountRate), + BreakevenDays: archeraMoney(e.Delta.BreakevenDays), + }, + ArcheraProductSupport: ps, + } +} + +func buildArcheraDTO(c *insurance.Comparison) archeraComparisonDTO { + dto := archeraComparisonDTO{ + Title: archeraTitle, + PlanID: archeraSanitize(c.PlanID), + FetchedAt: c.FetchedAt.UTC().Format("2006-01-02T15:04:05Z"), + Currency: archeraSanitizePtr(c.Currency), + PremiumIncluded: true, + MonthlyBasis: archeraMonthlyBasis, + UpfrontBasis: archeraUpfrontBasis, + PlanWide: archeraPlanWideNote, + Caveat: archeraCaveat, + Current: archeraTotals(c.Current), + Hypotheticals: []archeraHypotheticalDTO{}, + Rows: []archeraRowDTO{}, + NonGatingDisclosure: common.ArcheraNonGatingDisclosure, + SponsorshipDisclosure: common.ArcheraSponsorshipDisclosure, + } + for i := range c.Hypotheticals { + h := &c.Hypotheticals[i] + hd := archeraHypotheticalDTO{ + ContractTerm: archeraSanitizePtr(h.ContractTerm), + PaymentOption: archeraSanitize(string(h.PaymentOption)), + Totals: archeraTotals(h.Totals), + DeltaMonthlyNetSavings: archeraMoney(h.DeltaMonthlyNetSavings), + DeltaMonthlyCommitmentCost: archeraMoney(h.DeltaMonthlyCommitmentCost), + DeltaUpfrontCost: archeraMoney(h.DeltaUpfrontCost), + LineItems: []archeraLineItemDTO{}, + } + for _, li := range h.LineItems { + hd.LineItems = append(hd.LineItems, archeraLineItemDTO{ + LineItemID: archeraSanitize(li.LineItemID), + ActualTerm: archeraSanitizePtr(li.ActualTerm), + ActualPaymentOption: archeraPayment(li.ActualPaymentOption), + ActualCommitmentType: archeraSanitizePtr(li.ActualCommitmentType), + Reason: archeraSanitize(string(li.Reason)), + }) + } + dto.Hypotheticals = append(dto.Hypotheticals, hd) + } + for i := range c.Rows { + r := &c.Rows[i] + rd := archeraRowDTO{LineItemID: archeraSanitize(r.LineItemID), Current: archeraOffer(r.Current), Candidates: []archeraOfferDTO{}} + for j := range r.Candidates { + rd.Candidates = append(rd.Candidates, archeraOffer(r.Candidates[j])) + } + dto.Rows = append(dto.Rows, rd) + } + return dto +} + +func renderArcheraJSON(w io.Writer, dto archeraComparisonDTO) error { + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + return enc.Encode(dto) +} + +func archeraStr(s *string) string { + if s == nil { + return "unknown" + } + return *s +} + +func archeraTotalsLine(t archeraTotalsDTO) string { + m := t.Monthly + return fmt.Sprintf("commitment cost %s, cloud provider cost %s, premium %s, gross savings %s, net savings %s, covered on-demand %s (%s); upfront %s (%s)", + archeraStr(m.CommitmentCostTotal), archeraStr(m.CloudProviderCost), archeraStr(m.Premium), archeraStr(m.GrossSavings), + archeraStr(m.NetSavings), archeraStr(m.CoveredOnDemandCost), archeraMonthlyBasis, archeraStr(t.UpfrontCost), archeraUpfrontBasis) +} + +func renderArcheraTable(w io.Writer, d archeraComparisonDTO) error { + var b strings.Builder + p := func(format string, a ...any) { fmt.Fprintf(&b, format+"\n", a...) } + p("%s", d.Title) + p("Plan: %s Fetched: %s", d.PlanID, d.FetchedAt) + p("%s", archeraPremiumNote) + p("%s", d.PlanWide) + p("%s", d.Caveat) + p("") + p("Current totals: %s", archeraTotalsLine(d.Current)) + for i := range d.Hypotheticals { + h := &d.Hypotheticals[i] + p("") + p("Hypothetical %d: term %s, payment %s", i+1, archeraStr(h.ContractTerm), h.PaymentOption) + p(" totals: %s", archeraTotalsLine(h.Totals)) + p(" delta vs current: monthly net savings %s, monthly commitment cost %s, upfront %s", + archeraStr(h.DeltaMonthlyNetSavings), archeraStr(h.DeltaMonthlyCommitmentCost), archeraStr(h.DeltaUpfrontCost)) + for _, li := range h.LineItems { + p(" line item %s: term %s, payment %s, type %s, reason %s", li.LineItemID, archeraStr(li.ActualTerm), + archeraStr(li.ActualPaymentOption), archeraStr(li.ActualCommitmentType), li.Reason) + } + } + for i := range d.Rows { + r := &d.Rows[i] + p("") + p("Line item %s", r.LineItemID) + writeArcheraOffer(p, "current", r.Current) + for j := range r.Candidates { + writeArcheraOffer(p, "candidate", r.Candidates[j]) + } + } + p("") + p("%s", d.NonGatingDisclosure) + p("%s", d.SponsorshipDisclosure) + _, err := io.WriteString(w, b.String()) + return err +} + +func writeArcheraOffer(p func(string, ...any), kind string, o archeraOfferDTO) { + lease := "" + if o.LeaseAttached { + lease = ", lease attached" + } + p(" %s offer %s: %s %s, region %s, term %s, payment %s%s", kind, o.OfferID, o.Provider, o.CommitmentType, + archeraStr(o.Region), archeraStr(o.ContractTerm), archeraStr(o.PaymentOption), lease) + if o.ArcheraOfferName != nil { + p(" Archera offer name: %s", *o.ArcheraOfferName) + } + p(" discount rate %s (0-1 basis), breakeven days %s", archeraStr(o.DiscountRate), archeraStr(o.BreakevenDays)) + p(" %s", archeraTotalsLine(o.Totals)) + p(" delta vs current: monthly net savings %s, upfront %s, discount rate %s, breakeven days %s", + archeraStr(o.DeltaVsCurrent.MonthlyNetSavings), archeraStr(o.DeltaVsCurrent.UpfrontCost), + archeraStr(o.DeltaVsCurrent.DiscountRate), archeraStr(o.DeltaVsCurrent.BreakevenDays)) + if o.ArcheraProductSupport.Status == string(insurance.ProductSupportSupported) { + p(" Archera product support: supported (%s; %s)", o.ArcheraProductSupport.Source, o.ArcheraProductSupport.Evidence) + } else { + p(" Archera product support: unknown") + } +} diff --git a/docs/cli/archera-comparison.md b/docs/cli/archera-comparison.md new file mode 100644 index 000000000..eb087ff00 --- /dev/null +++ b/docs/cli/archera-comparison.md @@ -0,0 +1,51 @@ +# Archera plan comparison + +`ri-helper archera-comparison` prints a read-only comparison for one Archera +commitment plan. It never purchases, binds or submits anything, it is +independent of `--purchase`, and it makes no Archera request unless you run it. + +## Configuration + +The feature is off until configured. There is no flag for the API key. + +| Setting | Source | Secret | +|---------|--------|--------| +| `ARCHERA_API_KEY` | environment only | yes | +| `ARCHERA_ORG_ID` | `--org-id` or environment | no | +| `ARCHERA_PLAN_ID` | `--plan-id` or environment | no | + +The org and plan IDs are Archera UUIDs, not CUDly recommendation IDs. If a +setting is missing, the command exits with status 1 and names the missing +setting. It never prints a value. + +## Usage + +```bash +export ARCHERA_API_KEY=... # keep out of shell history and CI logs +export ARCHERA_ORG_ID= +export ARCHERA_PLAN_ID= +ri-helper archera-comparison # table +ri-helper archera-comparison --format json # one JSON document on stdout +``` + +Errors go to stderr and the exit status is 1 for every failure. A vendor error +shows the sanitized HTTP status and message, plus `Retry-After` when given +(capped at 24 hours). The command does not retry. + +## Reading the output + +- Totals include the Archera premium. The API reports no currency, so amounts + are unlabelled. +- Monthly figures are 730-hour monthly rates. Upfront figures are one-time and + are never added to monthly figures. +- Unknown values print `unknown` in the table and `null` in JSON, never `0`. +- Money is printed as an exact decimal. `discount_rate` is shown as sent + (0-1 basis). +- The comparison is a hypothetical rollup for the whole plan. It is not a + bindable quote, not a guarantee, and not mapped onto recommendation rows or + CSV output. +- Product support is `supported` only when Archera's documentation lists the + commitment type; otherwise `unknown`. +- Archera sponsors CUDly development. The command prints the same two + disclosures as the post-purchase message, in both formats + (`non_gating_disclosure` and `sponsorship_disclosure` in JSON). From 7082bb8d76259e8e8c576aa41076b8a651f01b58 Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sat, 10 Oct 2026 05:18:27 +0200 Subject: [PATCH 2/3] test(insurance): distinct-value golden fixture for archera-comparison Every money field in the fixture now carries a unique value (hundreds digit is the block, units digit the field) and table and JSON output are compared against golden files, so a swapped field mapping changes the output. One value is a long decimal that float64 cannot hold exactly. The vendor error cases use the real "message" body shape and assert the key is redacted. The credential-flag check matches any flag containing key, token, secret or password. Refs #2156 Co-Authored-By: Claude Sonnet 5.5 --- cmd/archera_comparison_test.go | 128 ++++++------------ cmd/testdata/archera_comparison.json | 1 + cmd/testdata/archera_json.golden | 190 +++++++++++++++++++++++++++ cmd/testdata/archera_table.golden | 38 ++++++ 4 files changed, 271 insertions(+), 86 deletions(-) create mode 100644 cmd/testdata/archera_comparison.json create mode 100644 cmd/testdata/archera_json.golden create mode 100644 cmd/testdata/archera_table.golden diff --git a/cmd/archera_comparison_test.go b/cmd/archera_comparison_test.go index 9c68162ad..ed06efab6 100644 --- a/cmd/archera_comparison_test.go +++ b/cmd/archera_comparison_test.go @@ -9,6 +9,9 @@ import ( "net/http" "net/http/httptest" "net/url" + "os" + "path/filepath" + "regexp" "strings" "sync" "testing" @@ -27,56 +30,19 @@ const ( archeraTestPlan = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" ) -// Synthetic fixtures shaped like the documented commitment-plans schema. -const archeraFinancialsFixture = `{ - "commitment_cost": {"total": 110, "breakdown": {"cloud_provider_cost": {"total": 100}, "archera_premium": 10.125}}, - "commitment_savings": {"net": null, "gross": 4.5}, - "covered_ondemand_cost": 104.5 -}` - -func archeraOfferFixture(isCurrent bool, offerType, lease, term, payment, name string) string { - return fmt.Sprintf(`{ - "is_current": %t, - "offer_id": "11111111-1111-4111-8111-111111111111", - "offer_org_id": "public", - "offer": {"provider": "aws", "type": %q, "region": "us-east-1", "guaranteed_display_name": %s}, - "lease_menu_item_id": %s, - "selected_amount": 3, - "commitment_type": %q, - "contract_term": %s, - "payment_option": %s, - "discount_rate": 0.3, - "breakeven_days": null, - "commitment_upfront_cost": 1200, - "commitment_financials_monthly_rate": %s, - "delta_vs_current": {"monthly_net_savings": 0, "upfront_cost": 0, "discount_rate": 0, "breakeven_days": null} - }`, isCurrent, offerType, name, lease, offerType, term, payment, archeraFinancialsFixture) -} - +// archeraComparisonFixture is a synthetic document shaped like the documented +// commitment-plans schema. Every money field carries a distinct value: the +// hundreds digit is the block (1 current totals, 2 and 6 hypotheticals, 3 +// current offer, 4 and 5 candidates) and the units digit is the field (1 cost +// total, 2 cloud cost, 3 premium, 4 gross, 5 net, 6 covered on-demand, 7 +// upfront, 8 and 9 deltas), so a swapped field mapping changes the output. The +// current-totals premium is a long decimal that float64 cannot hold exactly. func archeraComparisonFixture() string { - return `{ - "current_totals": {"commitment_financials_monthly_rate": ` + archeraFinancialsFixture + `, "commitment_upfront_cost": 1200}, - "hypothetical_totals": [{ - "contract_term": null, - "payment_option": "no_upfront", - "commitment_financials_monthly_rate": {}, - "commitment_upfront_cost": 0, - "delta_vs_current": {"monthly_net_savings": 7.25, "monthly_commitment_cost": -1, "upfront_cost": -1200}, - "line_items": [{ - "line_item_id": "22222222-2222-4222-8222-222222222222", - "actual_term": "one_year_gris", "actual_payment_option": "no_upfront", - "actual_commitment_type": "aws/AmazonEC2", - "actual_term_reason": "fallback_closest_shorter" - }] - }], - "data": [{ - "line_item_id": "22222222-2222-4222-8222-222222222222", - "current": ` + archeraOfferFixture(true, "aws/AmazonEC2", "null", "null", "null", "null") + `, - "candidates": [` + - archeraOfferFixture(false, "aws/AmazonEC2", `"33333333-3333-4333-8333-333333333333"`, `"one_year_gris"`, `"no_upfront"`, `"Offer\u001b[31m RED"`) + `,` + - archeraOfferFixture(false, "aws/SomethingNew", "null", `"one_year_gris"`, `"no_upfront"`, "null") + `] - }] - }` + b, err := os.ReadFile("testdata/archera_comparison.json") + if err != nil { + panic(err) + } + return string(b) } type archeraRecorder struct { @@ -202,6 +168,22 @@ func TestArcheraComparisonBadFormat(t *testing.T) { require.ErrorContains(t, err, "--format") } +var archeraFetchedRE = regexp.MustCompile(`(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)`) + +// checkArcheraGolden compares output with testdata/ after masking the +// wall-clock fetch time. ARCHERA_UPDATE_GOLDEN=1 rewrites the file. +func checkArcheraGolden(t *testing.T, name, got string) { + t.Helper() + got = archeraFetchedRE.ReplaceAllString(got, "") + path := filepath.Join("testdata", name) + if os.Getenv("ARCHERA_UPDATE_GOLDEN") == "1" { + require.NoError(t, os.WriteFile(path, []byte(got), 0o600)) + } + want, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, string(want), got) +} + func TestArcheraComparisonTable(t *testing.T) { rec := setupArchera(t, okHandler) out, errOut, err := runArchera(t, fullEnv()) @@ -214,24 +196,14 @@ func TestArcheraComparisonTable(t *testing.T) { assert.Empty(t, req.URL.RawQuery) assert.Equal(t, archeraTestKey, req.Header.Get("x-api-key")) - assert.Contains(t, out, archeraTitle) + checkArcheraGolden(t, "archera_table.golden", out) + assert.Contains(t, out, "never a bindable insurance quote") + assert.Contains(t, out, "103.1234567890123456789", "long decimal kept exact, not float64") assert.NotContains(t, strings.ToLower(out), "insured quote") - assert.Contains(t, out, "premium 10.125", "premium kept exact") - assert.Contains(t, out, "net savings unknown", "null money is unknown, not 0") - assert.Contains(t, out, "730-hour monthly rate") - assert.Contains(t, out, "one-time") - assert.Contains(t, out, "The API reports no currency") - assert.Contains(t, out, "reason fallback_closest_shorter") - assert.Contains(t, out, "lease attached") - assert.Contains(t, out, "Archera offer name: Offer[31m RED", "control characters stripped, text kept") assert.NotContains(t, out, "\x1b") - assert.Contains(t, out, "Archera product support: supported (") - assert.Contains(t, out, "Archera product support: unknown") - assert.Contains(t, out, "discount rate 0.3 (0-1 basis)") assert.Contains(t, out, common.ArcheraNonGatingDisclosure) assert.Contains(t, out, common.ArcheraSponsorshipDisclosure) assert.NotContains(t, out, common.ArcheraSignupURL) - assert.Contains(t, out, "not mapped onto CUDly recommendation rows") assert.NotContains(t, out+errOut, archeraTestKey) } @@ -240,30 +212,13 @@ func TestArcheraComparisonJSON(t *testing.T) { out, errOut, err := runArchera(t, fullEnv(), "--format", "json") require.NoError(t, err) assert.Empty(t, errOut) - var doc map[string]any dec := json.NewDecoder(strings.NewReader(out)) + var doc map[string]any require.NoError(t, dec.Decode(&doc), "stdout must be exactly one JSON document") assert.False(t, dec.More()) - assert.Nil(t, doc["currency"]) - assert.Equal(t, true, doc["premium_included"]) - assert.Equal(t, common.ArcheraNonGatingDisclosure, doc["non_gating_disclosure"]) - assert.Equal(t, common.ArcheraSponsorshipDisclosure, doc["sponsorship_disclosure"]) assert.NotContains(t, doc, "disclosures") - assert.NotContains(t, out, "\x1b") - assert.NotContains(t, out, `\u001b`) assert.NotContains(t, out, archeraTestKey) - - cur := doc["current_totals"].(map[string]any)["monthly_rate_730h"].(map[string]any) - assert.Equal(t, "10.125", cur["premium"]) - assert.Nil(t, cur["net_savings"]) - rows := doc["rows"].([]any) - cand := rows[0].(map[string]any)["candidates"].([]any) - support := cand[0].(map[string]any)["archera_product_support"].(map[string]any) - assert.Equal(t, "supported", support["status"]) - assert.NotEmpty(t, support["source"]) - unknown := cand[1].(map[string]any)["archera_product_support"].(map[string]any) - assert.Equal(t, "unknown", unknown["status"]) - assert.NotContains(t, unknown, "source") + checkArcheraGolden(t, "archera_json.golden", out) } func TestArcheraMoneyExact(t *testing.T) { @@ -292,9 +247,9 @@ func TestArcheraVendorErrorsAndRedaction(t *testing.T) { body string want string }{ - {"401", 401, nil, `{"detail":"nope"}`, "HTTP 401"}, - {"403 echoing key", 403, nil, `{"detail":"bad key ` + archeraTestKey + `"}`, "HTTP 403"}, - {"429", 429, map[string]string{"Retry-After": "30"}, `{"detail":"slow down"}`, "retry after 30s"}, + {"401", 401, nil, `{"message":"nope"}`, "HTTP 401"}, + {"403 echoing key", 403, nil, `{"message":"bad key ` + archeraTestKey + `"}`, "bad key [redacted]"}, + {"429", 429, map[string]string{"Retry-After": "30"}, `{"message":"slow down"}`, "retry after 30s"}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { @@ -338,8 +293,9 @@ func TestArcheraRedirectNotFollowed(t *testing.T) { } func TestArcheraNoKeyFlagAndStructsRedacted(t *testing.T) { - for _, name := range []string{"api-key", "key", "token", "secret", "archera-api-key"} { - assert.Nil(t, archeraComparisonCmd.Flags().Lookup(name), name) + usages := strings.ToLower(archeraComparisonCmd.Flags().FlagUsages()) + for _, bad := range []string{"key", "token", "secret", "password"} { + assert.NotContains(t, usages, bad, "a flag looks like a credential flag") } check := func(label string) { comparison, err := insurance.DecodeComparison(strings.NewReader(archeraComparisonFixture()), archeraTestOrg, archeraTestPlan, time.Unix(0, 0)) diff --git a/cmd/testdata/archera_comparison.json b/cmd/testdata/archera_comparison.json new file mode 100644 index 000000000..a3b4cdfd9 --- /dev/null +++ b/cmd/testdata/archera_comparison.json @@ -0,0 +1 @@ +{"current_totals":{"commitment_financials_monthly_rate":{"commitment_cost":{"total":101,"breakdown":{"cloud_provider_cost":{"total":102},"archera_premium":103.1234567890123456789}},"commitment_savings":{"net":105,"gross":104},"covered_ondemand_cost":106},"commitment_upfront_cost":107},"hypothetical_totals":[{"contract_term":"one_year_gris","payment_option":"no_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":201,"breakdown":{"cloud_provider_cost":{"total":202},"archera_premium":203}},"commitment_savings":{"net":205,"gross":204},"covered_ondemand_cost":206},"commitment_upfront_cost":207,"delta_vs_current":{"monthly_net_savings":208,"monthly_commitment_cost":209,"upfront_cost":210},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b1","actual_term":"six_month_gris","actual_payment_option":"partial_upfront","actual_commitment_type":"aws/AmazonEC2","actual_term_reason":"fallback_closest_shorter"}]},{"contract_term":null,"payment_option":"all_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":601,"breakdown":{"cloud_provider_cost":{"total":602},"archera_premium":603}},"commitment_savings":{"net":605,"gross":604},"covered_ondemand_cost":606},"commitment_upfront_cost":607,"delta_vs_current":{"monthly_net_savings":608,"monthly_commitment_cost":609,"upfront_cost":610},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b2","actual_term":null,"actual_payment_option":null,"actual_commitment_type":null,"actual_term_reason":"no_alternative"}]}],"data":[{"line_item_id":"70000000-0000-4000-8000-0000000000c1","current":{"is_current":true,"offer_id":"30000000-0000-4000-8000-000000000001","offer_org_id":"public","offer":{"provider":"aws","type":"aws/AmazonEC2","region":"us-east-1","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/AmazonEC2","contract_term":null,"payment_option":null,"discount_rate":0.31,"breakeven_days":31,"commitment_upfront_cost":307,"commitment_financials_monthly_rate":{"commitment_cost":{"total":301,"breakdown":{"cloud_provider_cost":{"total":302},"archera_premium":303}},"commitment_savings":{"net":305,"gross":304},"covered_ondemand_cost":306},"delta_vs_current":{"monthly_net_savings":308,"upfront_cost":309,"discount_rate":0.32,"breakeven_days":32}},"candidates":[{"is_current":false,"offer_id":"40000000-0000-4000-8000-000000000002","offer_org_id":"public","offer":{"provider":"aws","type":"aws/savingsplan/Compute","region":"eu-west-1","guaranteed_display_name":"Name Four"},"lease_menu_item_id":"40000000-0000-4000-8000-0000000000a1","selected_amount":3,"commitment_type":"aws/savingsplan/Compute","contract_term":"one_year_gris","payment_option":"no_upfront","discount_rate":0.41,"breakeven_days":41,"commitment_upfront_cost":407,"commitment_financials_monthly_rate":{"commitment_cost":{"total":401,"breakdown":{"cloud_provider_cost":{"total":402},"archera_premium":403}},"commitment_savings":{"net":405,"gross":404},"covered_ondemand_cost":406},"delta_vs_current":{"monthly_net_savings":408,"upfront_cost":409,"discount_rate":0.42,"breakeven_days":42}},{"is_current":false,"offer_id":"50000000-0000-4000-8000-000000000003","offer_org_id":"public","offer":{"provider":"aws","type":"aws/Unlisted","region":"ap-south-1","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/Unlisted","contract_term":"six_month_gris","payment_option":"all_upfront","discount_rate":0.51,"breakeven_days":null,"commitment_upfront_cost":507,"commitment_financials_monthly_rate":{"commitment_cost":{"total":501,"breakdown":{"cloud_provider_cost":{"total":502},"archera_premium":503}},"commitment_savings":{"net":null,"gross":504},"covered_ondemand_cost":506},"delta_vs_current":{"monthly_net_savings":508,"upfront_cost":509,"discount_rate":0.52,"breakeven_days":null}}]}]} diff --git a/cmd/testdata/archera_json.golden b/cmd/testdata/archera_json.golden new file mode 100644 index 000000000..fd05ef8ef --- /dev/null +++ b/cmd/testdata/archera_json.golden @@ -0,0 +1,190 @@ +{ + "title": "Archera commitment plan comparison (read-only)", + "plan_id": "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", + "fetched_at": "", + "currency": null, + "premium_included": true, + "monthly_basis": "730-hour monthly rate", + "upfront_basis": "one-time", + "plan_wide_note": "This comparison covers the whole Archera plan. It is not mapped onto CUDly recommendation rows or recommendation CSV output.", + "caveat": "A plan comparison is a hypothetical rollup, never a bindable insurance quote, and never a purchase. An insured target of 100% is a requested target subject to Archera underwriting allowances, not a guarantee. The API exposes no per-line product support or allowance verdict, so product support and allowance are shown as unknown unless Archera's documentation says otherwise.", + "current_totals": { + "monthly_rate_730h": { + "commitment_cost_total": "101", + "cloud_provider_cost": "102", + "premium": "103.1234567890123456789", + "gross_savings": "104", + "net_savings": "105", + "covered_on_demand_cost": "106" + }, + "upfront_cost_one_time": "107" + }, + "hypothetical_totals": [ + { + "contract_term": "one_year_gris", + "payment_option": "no_upfront", + "totals": { + "monthly_rate_730h": { + "commitment_cost_total": "201", + "cloud_provider_cost": "202", + "premium": "203", + "gross_savings": "204", + "net_savings": "205", + "covered_on_demand_cost": "206" + }, + "upfront_cost_one_time": "207" + }, + "delta_monthly_net_savings": "208", + "delta_monthly_commitment_cost": "209", + "delta_upfront_cost": "210", + "line_items": [ + { + "line_item_id": "60000000-0000-4000-8000-0000000000b1", + "actual_term": "six_month_gris", + "actual_payment_option": "partial_upfront", + "actual_commitment_type": "aws/AmazonEC2", + "reason": "fallback_closest_shorter" + } + ] + }, + { + "contract_term": null, + "payment_option": "all_upfront", + "totals": { + "monthly_rate_730h": { + "commitment_cost_total": "601", + "cloud_provider_cost": "602", + "premium": "603", + "gross_savings": "604", + "net_savings": "605", + "covered_on_demand_cost": "606" + }, + "upfront_cost_one_time": "607" + }, + "delta_monthly_net_savings": "608", + "delta_monthly_commitment_cost": "609", + "delta_upfront_cost": "610", + "line_items": [ + { + "line_item_id": "60000000-0000-4000-8000-0000000000b2", + "actual_term": null, + "actual_payment_option": null, + "actual_commitment_type": null, + "reason": "no_alternative" + } + ] + } + ], + "rows": [ + { + "line_item_id": "70000000-0000-4000-8000-0000000000c1", + "current": { + "is_current": true, + "offer_id": "30000000-0000-4000-8000-000000000001", + "commitment_type": "aws/AmazonEC2", + "provider": "aws", + "region": "us-east-1", + "contract_term": null, + "payment_option": null, + "lease_attached": false, + "archera_offer_name": null, + "discount_rate": "0.31", + "breakeven_days": "31", + "totals": { + "monthly_rate_730h": { + "commitment_cost_total": "301", + "cloud_provider_cost": "302", + "premium": "303", + "gross_savings": "304", + "net_savings": "305", + "covered_on_demand_cost": "306" + }, + "upfront_cost_one_time": "307" + }, + "delta_vs_current": { + "monthly_net_savings": "308", + "upfront_cost": "309", + "discount_rate": "0.32", + "breakeven_days": "32" + }, + "archera_product_support": { + "status": "supported", + "source": "https://docs.archera.ai/help-center/getting-started/supported-reservable-services", + "evidence": "AWS Reserved Instances table, service \"EC2\": Guaranteed terms \"30 day, 1 year\". Mapping of API type \"aws/AmazonEC2\" to page service \"EC2\" is inferred from the schema example; not stated by Archera." + } + }, + "candidates": [ + { + "is_current": false, + "offer_id": "40000000-0000-4000-8000-000000000002", + "commitment_type": "aws/savingsplan/Compute", + "provider": "aws", + "region": "eu-west-1", + "contract_term": "one_year_gris", + "payment_option": "no_upfront", + "lease_attached": true, + "archera_offer_name": "Name Four", + "discount_rate": "0.41", + "breakeven_days": "41", + "totals": { + "monthly_rate_730h": { + "commitment_cost_total": "401", + "cloud_provider_cost": "402", + "premium": "403", + "gross_savings": "404", + "net_savings": "405", + "covered_on_demand_cost": "406" + }, + "upfront_cost_one_time": "407" + }, + "delta_vs_current": { + "monthly_net_savings": "408", + "upfront_cost": "409", + "discount_rate": "0.42", + "breakeven_days": "42" + }, + "archera_product_support": { + "status": "supported", + "source": "https://docs.archera.ai/help-center/getting-started/supported-reservable-services", + "evidence": "AWS Savings Plans table, \"Compute Savings Plan\": Guaranteed terms \"30 day, 1 year\". Mapping of API type \"aws/savingsplan/Compute\" to page service \"Compute Savings Plan\" is inferred from the schema example; not stated by Archera." + } + }, + { + "is_current": false, + "offer_id": "50000000-0000-4000-8000-000000000003", + "commitment_type": "aws/Unlisted", + "provider": "aws", + "region": "ap-south-1", + "contract_term": "six_month_gris", + "payment_option": "all_upfront", + "lease_attached": false, + "archera_offer_name": null, + "discount_rate": "0.51", + "breakeven_days": null, + "totals": { + "monthly_rate_730h": { + "commitment_cost_total": "501", + "cloud_provider_cost": "502", + "premium": "503", + "gross_savings": "504", + "net_savings": null, + "covered_on_demand_cost": "506" + }, + "upfront_cost_one_time": "507" + }, + "delta_vs_current": { + "monthly_net_savings": "508", + "upfront_cost": "509", + "discount_rate": "0.52", + "breakeven_days": null + }, + "archera_product_support": { + "status": "unknown" + } + } + ] + } + ], + "non_gating_disclosure": "This is entirely optional. CUDly's purchase and management features work fully without Archera.", + "sponsorship_disclosure": "For full disclosure, Archera sponsors CUDly's Open Source development from a fraction of their insurance premiums." +} diff --git a/cmd/testdata/archera_table.golden b/cmd/testdata/archera_table.golden new file mode 100644 index 000000000..765f6b4df --- /dev/null +++ b/cmd/testdata/archera_table.golden @@ -0,0 +1,38 @@ +Archera commitment plan comparison (read-only) +Plan: bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb Fetched: +Totals include the Archera premium. The API reports no currency, so amounts are unlabelled. +This comparison covers the whole Archera plan. It is not mapped onto CUDly recommendation rows or recommendation CSV output. +A plan comparison is a hypothetical rollup, never a bindable insurance quote, and never a purchase. An insured target of 100% is a requested target subject to Archera underwriting allowances, not a guarantee. The API exposes no per-line product support or allowance verdict, so product support and allowance are shown as unknown unless Archera's documentation says otherwise. + +Current totals: commitment cost 101, cloud provider cost 102, premium 103.1234567890123456789, gross savings 104, net savings 105, covered on-demand 106 (730-hour monthly rate); upfront 107 (one-time) + +Hypothetical 1: term one_year_gris, payment no_upfront + totals: commitment cost 201, cloud provider cost 202, premium 203, gross savings 204, net savings 205, covered on-demand 206 (730-hour monthly rate); upfront 207 (one-time) + delta vs current: monthly net savings 208, monthly commitment cost 209, upfront 210 + line item 60000000-0000-4000-8000-0000000000b1: term six_month_gris, payment partial_upfront, type aws/AmazonEC2, reason fallback_closest_shorter + +Hypothetical 2: term unknown, payment all_upfront + totals: commitment cost 601, cloud provider cost 602, premium 603, gross savings 604, net savings 605, covered on-demand 606 (730-hour monthly rate); upfront 607 (one-time) + delta vs current: monthly net savings 608, monthly commitment cost 609, upfront 610 + line item 60000000-0000-4000-8000-0000000000b2: term unknown, payment unknown, type unknown, reason no_alternative + +Line item 70000000-0000-4000-8000-0000000000c1 + current offer 30000000-0000-4000-8000-000000000001: aws aws/AmazonEC2, region us-east-1, term unknown, payment unknown + discount rate 0.31 (0-1 basis), breakeven days 31 + commitment cost 301, cloud provider cost 302, premium 303, gross savings 304, net savings 305, covered on-demand 306 (730-hour monthly rate); upfront 307 (one-time) + delta vs current: monthly net savings 308, upfront 309, discount rate 0.32, breakeven days 32 + Archera product support: supported (https://docs.archera.ai/help-center/getting-started/supported-reservable-services; AWS Reserved Instances table, service "EC2": Guaranteed terms "30 day, 1 year". Mapping of API type "aws/AmazonEC2" to page service "EC2" is inferred from the schema example; not stated by Archera.) + candidate offer 40000000-0000-4000-8000-000000000002: aws aws/savingsplan/Compute, region eu-west-1, term one_year_gris, payment no_upfront, lease attached + Archera offer name: Name Four + discount rate 0.41 (0-1 basis), breakeven days 41 + commitment cost 401, cloud provider cost 402, premium 403, gross savings 404, net savings 405, covered on-demand 406 (730-hour monthly rate); upfront 407 (one-time) + delta vs current: monthly net savings 408, upfront 409, discount rate 0.42, breakeven days 42 + Archera product support: supported (https://docs.archera.ai/help-center/getting-started/supported-reservable-services; AWS Savings Plans table, "Compute Savings Plan": Guaranteed terms "30 day, 1 year". Mapping of API type "aws/savingsplan/Compute" to page service "Compute Savings Plan" is inferred from the schema example; not stated by Archera.) + candidate offer 50000000-0000-4000-8000-000000000003: aws aws/Unlisted, region ap-south-1, term six_month_gris, payment all_upfront + discount rate 0.51 (0-1 basis), breakeven days unknown + commitment cost 501, cloud provider cost 502, premium 503, gross savings 504, net savings unknown, covered on-demand 506 (730-hour monthly rate); upfront 507 (one-time) + delta vs current: monthly net savings 508, upfront 509, discount rate 0.52, breakeven days unknown + Archera product support: unknown + +This is entirely optional. CUDly's purchase and management features work fully without Archera. +For full disclosure, Archera sponsors CUDly's Open Source development from a fraction of their insurance premiums. From 66b752d9b2b321dc5499d7e1504914b897849e5b Mon Sep 17 00:00:00 2001 From: Cristian Magherusan-Stanciu Date: Sat, 10 Oct 2026 05:47:21 +0200 Subject: [PATCH 3/3] test(insurance): pin sanitization of every vendor string in archera-comparison The fixture now injects ESC and C1 characters (and an over-256-byte name) into the vendor string fields, the goldens pin the sanitized values, and a direct DTO test covers the fields the decoder would reject. The printed fetch time is asserted to be the current instant in UTC with the process zone shifted. Pkg-supplied product support text is no longer sanitized. Refs #2156 Co-Authored-By: Claude Sonnet 5.5 --- cmd/archera_comparison_test.go | 72 +++++++++++++++++++++++++++- cmd/archera_render.go | 2 +- cmd/testdata/archera_comparison.json | 2 +- cmd/testdata/archera_json.golden | 16 +++---- cmd/testdata/archera_table.golden | 12 ++--- 5 files changed, 87 insertions(+), 17 deletions(-) diff --git a/cmd/archera_comparison_test.go b/cmd/archera_comparison_test.go index ed06efab6..32a3ae88a 100644 --- a/cmd/archera_comparison_test.go +++ b/cmd/archera_comparison_test.go @@ -16,6 +16,7 @@ import ( "sync" "testing" "time" + "unicode" "github.com/LeanerCloud/cloud-commitments-go/pkg/common" "github.com/LeanerCloud/cloud-commitments-go/pkg/insurance" @@ -112,6 +113,9 @@ func runArchera(t *testing.T, env map[string]string, args ...string) (stdout, st t.Setenv(k, v) } resetHelpFlags() + savedLocal := time.Local + time.Local = time.FixedZone("archera-test", 5*3600+1800) + t.Cleanup(func() { time.Local = savedLocal }) archeraOpts.OrgID, archeraOpts.PlanID, archeraOpts.Format = "", "", "table" var out, errb bytes.Buffer rootCmd.SetOut(&out) @@ -170,10 +174,39 @@ func TestArcheraComparisonBadFormat(t *testing.T) { var archeraFetchedRE = regexp.MustCompile(`(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)`) +// assertArcheraFetchedUTC proves the printed fetch time is the current instant +// in UTC. runArchera shifts time.Local, so a local-zone time mislabelled "Z" +// would be hours off. +func assertArcheraFetchedUTC(t *testing.T, got string) { + t.Helper() + stamp := archeraFetchedRE.FindString(got) + require.NotEmpty(t, stamp) + require.True(t, strings.HasSuffix(stamp, "Z")) + parsed, err := time.Parse("2006-01-02T15:04:05Z", stamp) + require.NoError(t, err) + assert.WithinDuration(t, time.Now().UTC(), parsed, 2*time.Minute) +} + +// assertArcheraNoControlChars fails if any control character survives, in raw +// form or as a JSON escape. The fixture injects ESC and C1 characters into the +// vendor string fields. +func assertArcheraNoControlChars(t *testing.T, out string) { + t.Helper() + for _, r := range out { + if r != '\n' && unicode.IsControl(r) { + t.Fatalf("control character %U in output", r) + } + } + for _, esc := range []string{`\u001b`, `\u0085`, `\u009b`} { + assert.NotContains(t, out, esc) + } +} + // checkArcheraGolden compares output with testdata/ after masking the // wall-clock fetch time. ARCHERA_UPDATE_GOLDEN=1 rewrites the file. func checkArcheraGolden(t *testing.T, name, got string) { t.Helper() + assertArcheraFetchedUTC(t, got) got = archeraFetchedRE.ReplaceAllString(got, "") path := filepath.Join("testdata", name) if os.Getenv("ARCHERA_UPDATE_GOLDEN") == "1" { @@ -200,7 +233,7 @@ func TestArcheraComparisonTable(t *testing.T) { assert.Contains(t, out, "never a bindable insurance quote") assert.Contains(t, out, "103.1234567890123456789", "long decimal kept exact, not float64") assert.NotContains(t, strings.ToLower(out), "insured quote") - assert.NotContains(t, out, "\x1b") + assertArcheraNoControlChars(t, out) assert.Contains(t, out, common.ArcheraNonGatingDisclosure) assert.Contains(t, out, common.ArcheraSponsorshipDisclosure) assert.NotContains(t, out, common.ArcheraSignupURL) @@ -218,6 +251,7 @@ func TestArcheraComparisonJSON(t *testing.T) { assert.False(t, dec.More()) assert.NotContains(t, doc, "disclosures") assert.NotContains(t, out, archeraTestKey) + assertArcheraNoControlChars(t, out) checkArcheraGolden(t, "archera_json.golden", out) } @@ -313,3 +347,39 @@ func TestArcheraNoKeyFlagAndStructsRedacted(t *testing.T) { require.NoError(t, err) check("after") } + +// TestArcheraDTOSanitizesEveryVendorString builds the comparison directly, so +// fields the decoder would reject (enums, UUIDs, terms) still carry control +// characters into the DTO. +func TestArcheraDTOSanitizesEveryVendorString(t *testing.T) { + dirty := func(s string) string { return s + "\x1b[31m\u0085\u009b" } + str := func(s string) *string { v := dirty(s); return &v } + pay := insurance.PaymentOption(dirty("no_upfront")) + entry := func(id string) insurance.OfferEntry { + return insurance.OfferEntry{ + OfferID: dirty(id), CommitmentType: dirty("type" + id), Provider: common.ProviderType(dirty("aws")), + ContractTerm: str("term"), PaymentOption: &pay, Region: str("region"), GuaranteedDisplayName: str("name"), + LeaseMenuItemID: str("lease"), + } + } + c := &insurance.Comparison{ + PlanID: dirty("plan"), + Currency: str("usd"), + Hypotheticals: []insurance.Hypothetical{{ + ContractTerm: str("hterm"), PaymentOption: pay, + LineItems: []insurance.HypotheticalLineItem{{ + LineItemID: dirty("li"), ActualTerm: str("aterm"), ActualPaymentOption: &pay, + ActualCommitmentType: str("atype"), Reason: insurance.TermReason(dirty("exact_match")), + }}, + }}, + Rows: []insurance.ComparisonRow{{LineItemID: dirty("row"), Current: entry("c"), Candidates: []insurance.OfferEntry{entry("k")}}}, + } + var buf bytes.Buffer + dto := buildArcheraDTO(c) + require.NoError(t, renderArcheraJSON(&buf, dto)) + assertArcheraNoControlChars(t, buf.String()) + buf.Reset() + require.NoError(t, renderArcheraTable(&buf, dto)) + assertArcheraNoControlChars(t, buf.String()) + assert.Equal(t, "plan[31m", dto.PlanID) +} diff --git a/cmd/archera_render.go b/cmd/archera_render.go index ece6a3839..15587e51d 100644 --- a/cmd/archera_render.go +++ b/cmd/archera_render.go @@ -182,7 +182,7 @@ func archeraOffer(e insurance.OfferEntry) archeraOfferDTO { support := insurance.AssessProductSupport(e.Provider, e.CommitmentType) ps := archeraProductSupportDTO{Status: string(support.Status)} if support.Status == insurance.ProductSupportSupported { - ps.Source, ps.Evidence = archeraSanitize(support.Source), archeraSanitize(support.Evidence) + ps.Source, ps.Evidence = support.Source, support.Evidence } return archeraOfferDTO{ IsCurrent: e.IsCurrent, diff --git a/cmd/testdata/archera_comparison.json b/cmd/testdata/archera_comparison.json index a3b4cdfd9..0d6a6bbd7 100644 --- a/cmd/testdata/archera_comparison.json +++ b/cmd/testdata/archera_comparison.json @@ -1 +1 @@ -{"current_totals":{"commitment_financials_monthly_rate":{"commitment_cost":{"total":101,"breakdown":{"cloud_provider_cost":{"total":102},"archera_premium":103.1234567890123456789}},"commitment_savings":{"net":105,"gross":104},"covered_ondemand_cost":106},"commitment_upfront_cost":107},"hypothetical_totals":[{"contract_term":"one_year_gris","payment_option":"no_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":201,"breakdown":{"cloud_provider_cost":{"total":202},"archera_premium":203}},"commitment_savings":{"net":205,"gross":204},"covered_ondemand_cost":206},"commitment_upfront_cost":207,"delta_vs_current":{"monthly_net_savings":208,"monthly_commitment_cost":209,"upfront_cost":210},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b1","actual_term":"six_month_gris","actual_payment_option":"partial_upfront","actual_commitment_type":"aws/AmazonEC2","actual_term_reason":"fallback_closest_shorter"}]},{"contract_term":null,"payment_option":"all_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":601,"breakdown":{"cloud_provider_cost":{"total":602},"archera_premium":603}},"commitment_savings":{"net":605,"gross":604},"covered_ondemand_cost":606},"commitment_upfront_cost":607,"delta_vs_current":{"monthly_net_savings":608,"monthly_commitment_cost":609,"upfront_cost":610},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b2","actual_term":null,"actual_payment_option":null,"actual_commitment_type":null,"actual_term_reason":"no_alternative"}]}],"data":[{"line_item_id":"70000000-0000-4000-8000-0000000000c1","current":{"is_current":true,"offer_id":"30000000-0000-4000-8000-000000000001","offer_org_id":"public","offer":{"provider":"aws","type":"aws/AmazonEC2","region":"us-east-1","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/AmazonEC2","contract_term":null,"payment_option":null,"discount_rate":0.31,"breakeven_days":31,"commitment_upfront_cost":307,"commitment_financials_monthly_rate":{"commitment_cost":{"total":301,"breakdown":{"cloud_provider_cost":{"total":302},"archera_premium":303}},"commitment_savings":{"net":305,"gross":304},"covered_ondemand_cost":306},"delta_vs_current":{"monthly_net_savings":308,"upfront_cost":309,"discount_rate":0.32,"breakeven_days":32}},"candidates":[{"is_current":false,"offer_id":"40000000-0000-4000-8000-000000000002","offer_org_id":"public","offer":{"provider":"aws","type":"aws/savingsplan/Compute","region":"eu-west-1","guaranteed_display_name":"Name Four"},"lease_menu_item_id":"40000000-0000-4000-8000-0000000000a1","selected_amount":3,"commitment_type":"aws/savingsplan/Compute","contract_term":"one_year_gris","payment_option":"no_upfront","discount_rate":0.41,"breakeven_days":41,"commitment_upfront_cost":407,"commitment_financials_monthly_rate":{"commitment_cost":{"total":401,"breakdown":{"cloud_provider_cost":{"total":402},"archera_premium":403}},"commitment_savings":{"net":405,"gross":404},"covered_ondemand_cost":406},"delta_vs_current":{"monthly_net_savings":408,"upfront_cost":409,"discount_rate":0.42,"breakeven_days":42}},{"is_current":false,"offer_id":"50000000-0000-4000-8000-000000000003","offer_org_id":"public","offer":{"provider":"aws","type":"aws/Unlisted","region":"ap-south-1","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/Unlisted","contract_term":"six_month_gris","payment_option":"all_upfront","discount_rate":0.51,"breakeven_days":null,"commitment_upfront_cost":507,"commitment_financials_monthly_rate":{"commitment_cost":{"total":501,"breakdown":{"cloud_provider_cost":{"total":502},"archera_premium":503}},"commitment_savings":{"net":null,"gross":504},"covered_ondemand_cost":506},"delta_vs_current":{"monthly_net_savings":508,"upfront_cost":509,"discount_rate":0.52,"breakeven_days":null}}]}]} +{"current_totals":{"commitment_financials_monthly_rate":{"commitment_cost":{"total":101,"breakdown":{"cloud_provider_cost":{"total":102},"archera_premium":103.1234567890123456789}},"commitment_savings":{"net":105,"gross":104},"covered_ondemand_cost":106},"commitment_upfront_cost":107},"hypothetical_totals":[{"contract_term":"one_year_gris","payment_option":"no_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":201,"breakdown":{"cloud_provider_cost":{"total":202},"archera_premium":203}},"commitment_savings":{"net":205,"gross":204},"covered_ondemand_cost":206},"commitment_upfront_cost":207,"delta_vs_current":{"monthly_net_savings":208,"monthly_commitment_cost":209,"upfront_cost":210},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b1\u001b[31m\u0085","actual_term":"six_month_gris","actual_payment_option":"partial_upfront","actual_commitment_type":"aws/AmazonEC2\u001b[31m\u0085","actual_term_reason":"fallback_closest_shorter"}]},{"contract_term":null,"payment_option":"all_upfront","commitment_financials_monthly_rate":{"commitment_cost":{"total":601,"breakdown":{"cloud_provider_cost":{"total":602},"archera_premium":603}},"commitment_savings":{"net":605,"gross":604},"covered_ondemand_cost":606},"commitment_upfront_cost":607,"delta_vs_current":{"monthly_net_savings":608,"monthly_commitment_cost":609,"upfront_cost":610},"line_items":[{"line_item_id":"60000000-0000-4000-8000-0000000000b2","actual_term":null,"actual_payment_option":null,"actual_commitment_type":null,"actual_term_reason":"no_alternative"}]}],"data":[{"line_item_id":"70000000-0000-4000-8000-0000000000c1\u001b[31m\u0085","current":{"is_current":true,"offer_id":"30000000-0000-4000-8000-000000000001\u001b[31m\u0085","offer_org_id":"public","offer":{"provider":"aws","type":"aws/AmazonEC2","region":"us-east-1\u001b[31m\u0085","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/AmazonEC2","contract_term":null,"payment_option":null,"discount_rate":0.31,"breakeven_days":31,"commitment_upfront_cost":307,"commitment_financials_monthly_rate":{"commitment_cost":{"total":301,"breakdown":{"cloud_provider_cost":{"total":302},"archera_premium":303}},"commitment_savings":{"net":305,"gross":304},"covered_ondemand_cost":306},"delta_vs_current":{"monthly_net_savings":308,"upfront_cost":309,"discount_rate":0.32,"breakeven_days":32}},"candidates":[{"is_current":false,"offer_id":"40000000-0000-4000-8000-000000000002","offer_org_id":"public","offer":{"provider":"aws","type":"aws/savingsplan/Compute","region":"eu-west-1\u001b[31m\u0085","guaranteed_display_name":"Name\u001b[31m\u0085FourNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN"},"lease_menu_item_id":"40000000-0000-4000-8000-0000000000a1","selected_amount":3,"commitment_type":"aws/savingsplan/Compute","contract_term":"one_year_gris","payment_option":"no_upfront","discount_rate":0.41,"breakeven_days":41,"commitment_upfront_cost":407,"commitment_financials_monthly_rate":{"commitment_cost":{"total":401,"breakdown":{"cloud_provider_cost":{"total":402},"archera_premium":403}},"commitment_savings":{"net":405,"gross":404},"covered_ondemand_cost":406},"delta_vs_current":{"monthly_net_savings":408,"upfront_cost":409,"discount_rate":0.42,"breakeven_days":42}},{"is_current":false,"offer_id":"50000000-0000-4000-8000-000000000003","offer_org_id":"public","offer":{"provider":"aws","type":"aws/Unlisted\u001b[31m\u0085","region":"ap-south-1","guaranteed_display_name":null},"lease_menu_item_id":null,"selected_amount":3,"commitment_type":"aws/Unlisted\u001b[31m\u0085","contract_term":"six_month_gris","payment_option":"all_upfront","discount_rate":0.51,"breakeven_days":null,"commitment_upfront_cost":507,"commitment_financials_monthly_rate":{"commitment_cost":{"total":501,"breakdown":{"cloud_provider_cost":{"total":502},"archera_premium":503}},"commitment_savings":{"net":null,"gross":504},"covered_ondemand_cost":506},"delta_vs_current":{"monthly_net_savings":508,"upfront_cost":509,"discount_rate":0.52,"breakeven_days":null}}]}]} diff --git a/cmd/testdata/archera_json.golden b/cmd/testdata/archera_json.golden index fd05ef8ef..28e8221f6 100644 --- a/cmd/testdata/archera_json.golden +++ b/cmd/testdata/archera_json.golden @@ -39,10 +39,10 @@ "delta_upfront_cost": "210", "line_items": [ { - "line_item_id": "60000000-0000-4000-8000-0000000000b1", + "line_item_id": "60000000-0000-4000-8000-0000000000b1[31m", "actual_term": "six_month_gris", "actual_payment_option": "partial_upfront", - "actual_commitment_type": "aws/AmazonEC2", + "actual_commitment_type": "aws/AmazonEC2[31m", "reason": "fallback_closest_shorter" } ] @@ -77,13 +77,13 @@ ], "rows": [ { - "line_item_id": "70000000-0000-4000-8000-0000000000c1", + "line_item_id": "70000000-0000-4000-8000-0000000000c1[31m", "current": { "is_current": true, - "offer_id": "30000000-0000-4000-8000-000000000001", + "offer_id": "30000000-0000-4000-8000-000000000001[31m", "commitment_type": "aws/AmazonEC2", "provider": "aws", - "region": "us-east-1", + "region": "us-east-1[31m", "contract_term": null, "payment_option": null, "lease_attached": false, @@ -119,11 +119,11 @@ "offer_id": "40000000-0000-4000-8000-000000000002", "commitment_type": "aws/savingsplan/Compute", "provider": "aws", - "region": "eu-west-1", + "region": "eu-west-1[31m", "contract_term": "one_year_gris", "payment_option": "no_upfront", "lease_attached": true, - "archera_offer_name": "Name Four", + "archera_offer_name": "Name[31mFourNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN", "discount_rate": "0.41", "breakeven_days": "41", "totals": { @@ -152,7 +152,7 @@ { "is_current": false, "offer_id": "50000000-0000-4000-8000-000000000003", - "commitment_type": "aws/Unlisted", + "commitment_type": "aws/Unlisted[31m", "provider": "aws", "region": "ap-south-1", "contract_term": "six_month_gris", diff --git a/cmd/testdata/archera_table.golden b/cmd/testdata/archera_table.golden index 765f6b4df..4b83cd6c3 100644 --- a/cmd/testdata/archera_table.golden +++ b/cmd/testdata/archera_table.golden @@ -9,26 +9,26 @@ Current totals: commitment cost 101, cloud provider cost 102, premium 103.123456 Hypothetical 1: term one_year_gris, payment no_upfront totals: commitment cost 201, cloud provider cost 202, premium 203, gross savings 204, net savings 205, covered on-demand 206 (730-hour monthly rate); upfront 207 (one-time) delta vs current: monthly net savings 208, monthly commitment cost 209, upfront 210 - line item 60000000-0000-4000-8000-0000000000b1: term six_month_gris, payment partial_upfront, type aws/AmazonEC2, reason fallback_closest_shorter + line item 60000000-0000-4000-8000-0000000000b1[31m: term six_month_gris, payment partial_upfront, type aws/AmazonEC2[31m, reason fallback_closest_shorter Hypothetical 2: term unknown, payment all_upfront totals: commitment cost 601, cloud provider cost 602, premium 603, gross savings 604, net savings 605, covered on-demand 606 (730-hour monthly rate); upfront 607 (one-time) delta vs current: monthly net savings 608, monthly commitment cost 609, upfront 610 line item 60000000-0000-4000-8000-0000000000b2: term unknown, payment unknown, type unknown, reason no_alternative -Line item 70000000-0000-4000-8000-0000000000c1 - current offer 30000000-0000-4000-8000-000000000001: aws aws/AmazonEC2, region us-east-1, term unknown, payment unknown +Line item 70000000-0000-4000-8000-0000000000c1[31m + current offer 30000000-0000-4000-8000-000000000001[31m: aws aws/AmazonEC2, region us-east-1[31m, term unknown, payment unknown discount rate 0.31 (0-1 basis), breakeven days 31 commitment cost 301, cloud provider cost 302, premium 303, gross savings 304, net savings 305, covered on-demand 306 (730-hour monthly rate); upfront 307 (one-time) delta vs current: monthly net savings 308, upfront 309, discount rate 0.32, breakeven days 32 Archera product support: supported (https://docs.archera.ai/help-center/getting-started/supported-reservable-services; AWS Reserved Instances table, service "EC2": Guaranteed terms "30 day, 1 year". Mapping of API type "aws/AmazonEC2" to page service "EC2" is inferred from the schema example; not stated by Archera.) - candidate offer 40000000-0000-4000-8000-000000000002: aws aws/savingsplan/Compute, region eu-west-1, term one_year_gris, payment no_upfront, lease attached - Archera offer name: Name Four + candidate offer 40000000-0000-4000-8000-000000000002: aws aws/savingsplan/Compute, region eu-west-1[31m, term one_year_gris, payment no_upfront, lease attached + Archera offer name: Name[31mFourNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN discount rate 0.41 (0-1 basis), breakeven days 41 commitment cost 401, cloud provider cost 402, premium 403, gross savings 404, net savings 405, covered on-demand 406 (730-hour monthly rate); upfront 407 (one-time) delta vs current: monthly net savings 408, upfront 409, discount rate 0.42, breakeven days 42 Archera product support: supported (https://docs.archera.ai/help-center/getting-started/supported-reservable-services; AWS Savings Plans table, "Compute Savings Plan": Guaranteed terms "30 day, 1 year". Mapping of API type "aws/savingsplan/Compute" to page service "Compute Savings Plan" is inferred from the schema example; not stated by Archera.) - candidate offer 50000000-0000-4000-8000-000000000003: aws aws/Unlisted, region ap-south-1, term six_month_gris, payment all_upfront + candidate offer 50000000-0000-4000-8000-000000000003: aws aws/Unlisted[31m, region ap-south-1, term six_month_gris, payment all_upfront discount rate 0.51 (0-1 basis), breakeven days unknown commitment cost 501, cloud provider cost 502, premium 503, gross savings 504, net savings unknown, covered on-demand 506 (730-hour monthly rate); upfront 507 (one-time) delta vs current: monthly net savings 508, upfront 509, discount rate 0.52, breakeven days unknown