package oci import ( "context" "os" "path/filepath" "strings" "testing" "time" "oci-portal/internal/model" ) // testKeyDir 指向仓库根的真实 API Key 测试资料目录。 const testKeyDir = "../../../test-api-key" // keyCase 把测试 key 文件名映射到期望账户类别;expect 为空表示只打印不断言。 type keyCase struct { name string expect string } func integrationKeyCases(t *testing.T) []keyCase { t.Helper() all := []keyCase{ {name: "试用期", expect: model.AccountTypeTrial}, {name: "付费", expect: model.AccountTypePaid}, {name: "免费01", expect: model.AccountTypeFree}, {name: "免费02", expect: model.AccountTypeFree}, {name: "未知01"}, {name: "未知02"}, } if name := os.Getenv("OCI_TEST_KEY"); name != "" { for _, k := range all { if k.name == name { return []keyCase{k} } } t.Fatalf("OCI_TEST_KEY=%q not found in test-api-key", name) } if os.Getenv("OCI_TEST_ALL_KEYS") == "1" { return all } // 默认只使用试用期 key,避免不必要地触碰其他真实账号 return all[:1] } func loadTestCredentials(t *testing.T, name string) Credentials { t.Helper() iniPath := filepath.Join(testKeyDir, name+"-api-key.ini") pemPath := filepath.Join(testKeyDir, name+"-api-key.pem") iniText, err := os.ReadFile(iniPath) if err != nil { t.Fatalf("read %s: %v", iniPath, err) } pem, err := os.ReadFile(pemPath) if err != nil { t.Fatalf("read %s: %v", pemPath, err) } cred, err := ParseConfigINI(string(iniText)) if err != nil { t.Fatalf("parse %s: %v", iniPath, err) } cred.PrivateKey = string(pem) if err := cred.Validate(); err != nil { t.Fatalf("validate credentials of %s: %v", name, err) } return cred } // TestRealOCITenantRegionsAndLimits 用试用期 key 验证区域订阅列表和 // 配额查询(均为只读调用)。订阅新区域不可撤销,不做真实测试。 func TestRealOCITenantRegionsAndLimits(t *testing.T) { if os.Getenv("OCI_INTEGRATION_TEST") != "1" { t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests") } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cred := loadTestCredentials(t, "试用期") client := NewClient() subs, err := client.ListRegionSubscriptions(ctx, cred) if err != nil { t.Fatalf("ListRegionSubscriptions: %v", err) } if len(subs) == 0 { t.Fatal("ListRegionSubscriptions returned empty list, want at least home region") } hasHome := false for _, sub := range subs { t.Logf("region key=%s name=%s status=%s home=%v", sub.Key, sub.Name, sub.Status, sub.IsHomeRegion) hasHome = hasHome || sub.IsHomeRegion } if !hasHome { t.Error("no home region in subscriptions") } values, err := client.ListLimits(ctx, cred, LimitsQuery{ Service: "compute", Name: "standard-a1-core-count", WithAvailability: true, }) if err != nil { t.Fatalf("ListLimits: %v", err) } if len(values) == 0 { t.Fatal("ListLimits returned empty list for standard-a1-core-count") } for _, v := range values { used, available := int64(-1), int64(-1) if v.Used != nil { used = *v.Used } if v.Available != nil { available = *v.Available } t.Logf("limit name=%s scope=%s ad=%s value=%d used=%d available=%d", v.Name, v.ScopeType, v.AvailabilityDomain, v.Value, used, available) } // 模糊过滤应比精确名命中更多配额(a1 与 e2 等系列都含 core-count) fuzzy, err := client.ListLimits(ctx, cred, LimitsQuery{Service: "compute", Name: "core-count"}) if err != nil { t.Fatalf("ListLimits fuzzy: %v", err) } t.Logf("fuzzy core-count matched %d entries", len(fuzzy)) if len(fuzzy) <= len(values) { t.Errorf("fuzzy matches = %d, want more than exact matches %d", len(fuzzy), len(values)) } services, err := client.ListLimitServices(ctx, cred, "") if err != nil { t.Fatalf("ListLimitServices: %v", err) } if len(services) == 0 { t.Fatal("ListLimitServices returned empty list") } hasCompute := false for _, s := range services { hasCompute = hasCompute || s.Name == "compute" } if !hasCompute { t.Errorf("services (%d) missing compute", len(services)) } t.Logf("limit services: %d entries, first=%+v", len(services), services[0]) } // TestRealOCISubscriptions 用试用期 key 验证订阅列表与订阅详情(只读)。 func TestRealOCISubscriptions(t *testing.T) { if os.Getenv("OCI_INTEGRATION_TEST") != "1" { t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests") } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cred := loadTestCredentials(t, "试用期") client := NewClient() subs, err := client.ListSubscriptions(ctx, cred) if err != nil { t.Fatalf("ListSubscriptions: %v", err) } if len(subs) == 0 { t.Fatal("ListSubscriptions returned empty list") } cloudcmID := "" for _, sub := range subs { t.Logf("subscription id=%s service=%s created=%v", sub.ID, sub.ServiceName, sub.TimeCreated) if strings.EqualFold(sub.ServiceName, cloudcmServiceName) { cloudcmID = sub.ID } } if cloudcmID == "" { t.Fatal("no CLOUDCM subscription in list") } detail, err := client.GetSubscription(ctx, cred, cloudcmID) if err != nil { t.Fatalf("GetSubscription: %v", err) } if detail.ID != cloudcmID { t.Errorf("detail.ID = %q, want %q", detail.ID, cloudcmID) } t.Logf("detail service=%s paymentModel=%q tier=%q state=%s currency=%s region=%s promotions=%d", detail.ServiceName, detail.PaymentModel, detail.SubscriptionTier, detail.LifecycleState, detail.CloudAmountCurrency, detail.RegionAssignment, len(detail.Promotions)) for _, p := range detail.Promotions { t.Logf("promotion status=%s amount=%.2f %s expires=%v intentToPay=%v", p.Status, p.Amount, p.CurrencyUnit, p.TimeExpired, p.IsIntentToPay) } if len(detail.Promotions) == 0 { t.Error("trial subscription has no promotions, want at least one") } } // TestRealOCIVerifyAndClassify 走真实 OCI 验证测活与账户类别判定, // 必须显式设置 OCI_INTEGRATION_TEST=1 才会执行。 func TestRealOCIVerifyAndClassify(t *testing.T) { if os.Getenv("OCI_INTEGRATION_TEST") != "1" { t.Skip("set OCI_INTEGRATION_TEST=1 to run real OCI tests") } client := NewClient() for _, tc := range integrationKeyCases(t) { t.Run(tc.name, func(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) defer cancel() cred := loadTestCredentials(t, tc.name) info, err := client.ValidateKey(ctx, cred) if err != nil { t.Fatalf("ValidateKey: %v", err) } t.Logf("tenancy=%s homeRegion=%s", info.Name, info.HomeRegionKey) profile, err := client.FetchAccountProfile(ctx, cred) if err != nil { t.Fatalf("FetchAccountProfile: %v", err) } t.Logf("type=%s paymentModel=%q tier=%q promo=%q amount=%.2f service=%s", profile.AccountType, profile.PaymentModel, profile.SubscriptionTier, profile.PromotionStatus, profile.PromotionAmount, profile.ServiceName) if !strings.EqualFold(profile.ServiceName, cloudcmServiceName) { t.Errorf("ServiceName = %q, want %q", profile.ServiceName, cloudcmServiceName) } if tc.expect != "" && profile.AccountType != tc.expect { t.Errorf("AccountType = %q, want %q", profile.AccountType, tc.expect) } }) } }