package oci import ( "context" "fmt" "os" "strings" "testing" "time" ) // TestRealOCITenantUsers 验证用户管理全链路:新增 → 重置密码 → // 清 MFA → 清 API Key → 删除,全部只操作新建的测试用户。 func TestRealOCITenantUsers(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(), 5*time.Minute) defer cancel() cred := loadTestCredentials(t, "试用期") client := NewClient() homeRegion := cred.Region users, err := client.ListTenantUsers(ctx, cred, homeRegion, "") if err != nil { t.Fatalf("ListTenantUsers: %v", err) } if len(users) == 0 { t.Fatal("no tenant users, want at least the current one") } currentSeen := false for _, u := range users { if u.IsCurrentUser { currentSeen = true } } if !currentSeen { t.Error("current user not flagged in list") } t.Logf("tenant users: %d", len(users)) name := fmt.Sprintf("oci-portal-e2e-user-%d", time.Now().Unix()) user, err := client.CreateTenantUser(ctx, cred, homeRegion, "", CreateTenantUserInput{ Name: name, Description: "e2e temp user", Email: name + "@example.com", GivenName: "E2E", FamilyName: "Temp", }) if err != nil { t.Fatalf("CreateTenantUser: %v", err) } t.Logf("created user %s (%s)", user.Name, user.ID) defer func() { if err := client.DeleteTenantUser(ctx, cred, homeRegion, "", user.ID); err != nil { t.Errorf("cleanup: DeleteTenantUser: %v", err) } else { t.Log("cleanup: user deleted") } }() newDesc, newEmail, newGiven := "e2e updated", name+"+upd@example.com", "E2E2" updated, err := client.UpdateTenantUser(ctx, cred, homeRegion, "", user.ID, UpdateTenantUserInput{ Description: &newDesc, Email: &newEmail, GivenName: &newGiven, }) if err != nil { t.Fatalf("UpdateTenantUser: %v", err) } if updated.Description != newDesc { t.Errorf("updated description = %q, want %q", updated.Description, newDesc) } t.Logf("updated user desc=%q email=%q", updated.Description, updated.Email) password, err := client.ResetTenantUserPassword(ctx, cred, homeRegion, "", user.ID) if err != nil { t.Fatalf("ResetTenantUserPassword: %v", err) } if password == "" { t.Error("reset password returned empty password") } t.Logf("password reset ok (len=%d)", len(password)) mfaDeleted, err := client.DeleteTenantUserMfaDevices(ctx, cred, homeRegion, "", user.ID) if err != nil { t.Fatalf("DeleteTenantUserMfaDevices: %v", err) } t.Logf("mfa devices deleted: %d", mfaDeleted) keysDeleted, err := client.DeleteTenantUserApiKeys(ctx, cred, homeRegion, user.ID, false) if err != nil { t.Fatalf("DeleteTenantUserApiKeys: %v", err) } t.Logf("api keys deleted: %d", keysDeleted) } // TestRealOCIDomainSettings 验证通知收件人与密码策略的改后恢复。 func TestRealOCIDomainSettings(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(), 5*time.Minute) defer cancel() cred := loadTestCredentials(t, "试用期") client := NewClient() region := cred.Region original, err := client.GetNotificationRecipients(ctx, cred, region, "") if err != nil { t.Fatalf("GetNotificationRecipients: %v", err) } t.Logf("original recipients=%v testMode=%v", original.Recipients, original.TestModeEnabled) updated, err := client.UpdateNotificationRecipients(ctx, cred, region, "", []string{"e2e@example.com"}) if err != nil { t.Fatalf("UpdateNotificationRecipients: %v", err) } if !updated.TestModeEnabled || len(updated.Recipients) != 1 { t.Errorf("updated = %+v, want test mode with 1 recipient", updated) } restoreRecipients := original.Recipients if !original.TestModeEnabled { restoreRecipients = nil } if _, err := client.UpdateNotificationRecipients(ctx, cred, region, "", restoreRecipients); err != nil { t.Errorf("restore recipients: %v", err) } else { t.Log("recipients restored") } policies, err := client.ListPasswordPolicies(ctx, cred, region, "") if err != nil { t.Fatalf("ListPasswordPolicies: %v", err) } target, ok := pickCustomPolicy(policies) if !ok { t.Fatalf("no Custom password policy among %d policies (built-ins are read-only)", len(policies)) } t.Logf("policies: %d, patch target %s strength=%s expires=%v", len(policies), target.Name, target.PasswordStrength, target.PasswordExpiresAfter) days := 350 patched, err := client.UpdatePasswordPolicy(ctx, cred, region, "", target.ID, UpdatePasswordPolicyInput{PasswordExpiresAfter: &days}) if err != nil { t.Fatalf("UpdatePasswordPolicy: %v", err) } if patched.PasswordExpiresAfter == nil || *patched.PasswordExpiresAfter != days { t.Errorf("passwordExpiresAfter = %v, want %d", patched.PasswordExpiresAfter, days) } restore := 0 if target.PasswordExpiresAfter != nil { restore = *target.PasswordExpiresAfter } if _, err := client.UpdatePasswordPolicy(ctx, cred, region, "", target.ID, UpdatePasswordPolicyInput{PasswordExpiresAfter: &restore}); err != nil { t.Errorf("restore password policy: %v", err) } else { t.Logf("policy restored to %d", restore) } } // pickCustomPolicy 返回 Custom 强度的密码策略;Simple/Standard 内置策略只读不可改。 func pickCustomPolicy(policies []PasswordPolicyInfo) (PasswordPolicyInfo, bool) { for _, p := range policies { if strings.EqualFold(p.PasswordStrength, "custom") { return p, true } } return PasswordPolicyInfo{}, false } // TestRealOCIUsageAndTraffic 验证成本分析与实例流量统计: // 成本查最近 7 天;流量需要真实 VNIC,无实例时创建一台临时 Micro 实例。 func TestRealOCIUsageAndTraffic(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(), 15*time.Minute) defer cancel() cred := loadTestCredentials(t, "试用期") client := NewClient() end := time.Now().UTC().Truncate(24*time.Hour).AddDate(0, 0, 1) items, err := client.SummarizeCosts(ctx, cred, CostQuery{ StartTime: end.AddDate(0, 0, -7), EndTime: end, Granularity: "DAILY", QueryType: "COST", GroupBy: "service", }) if err != nil { t.Fatalf("SummarizeCosts: %v", err) } t.Logf("cost items: %d", len(items)) for i, item := range items { if i >= 3 { break } t.Logf(" %s %s %.4f %s", item.TimeStart, item.GroupValue, item.ComputedAmount, item.Currency) } instanceID := ensureTrafficInstance(ctx, t, client, cred) traffic, err := client.SummarizeInstanceTraffic(ctx, cred, TrafficQuery{ InstanceID: instanceID, StartTime: end.AddDate(0, 0, -7), EndTime: end, }) if err != nil { t.Fatalf("SummarizeInstanceTraffic: %v", err) } t.Logf("traffic vnics=%d in=%.0fB out=%.0fB", len(traffic.Vnics), traffic.InboundBytes, traffic.OutboundBytes) if len(traffic.Vnics) == 0 { t.Error("no vnic in traffic result, want at least one") } } // ensureTrafficInstance 返回一个可查流量的实例:已有实例直接用, // 否则创建一台临时 E2 Micro(测试结束删除)。 func ensureTrafficInstance(ctx context.Context, t *testing.T, client *RealClient, cred Credentials) string { t.Helper() instances, err := client.ListInstances(ctx, cred, "") if err != nil { t.Fatalf("ListInstances: %v", err) } for _, in := range instances { if in.LifecycleState == "RUNNING" || in.LifecycleState == "STOPPED" { t.Logf("using existing instance %s", in.ID) return in.ID } } images, err := client.ListImages(ctx, cred, ImagesQuery{OperatingSystem: "Canonical Ubuntu", Shape: "VM.Standard.E2.1.Micro"}) if err != nil || len(images) == 0 { t.Skipf("no micro image available: %v", err) } instance, err := client.LaunchInstance(ctx, cred, CreateInstanceInput{ DisplayName: fmt.Sprintf("oci-portal-e2e-traffic-%d", time.Now().Unix()), Shape: "VM.Standard.E2.1.Micro", ImageID: images[0].ID, AssignPublicIP: false, SSHPublicKey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEfake+e2e+key oci-portal-e2e", }) if err != nil { if strings.Contains(err.Error(), "capacity") || strings.Contains(err.Error(), "LimitExceeded") { t.Skipf("micro capacity unavailable: %v", err) } t.Fatalf("LaunchInstance: %v", err) } t.Cleanup(func() { terminateAndWait(t, client, cred, instance.ID) cleanupAutoVCNIfAny(t, client, cred) }) // VNIC attachment 在实例 RUNNING 后才就绪,否则流量查询列不到 VNIC if err := waitInstanceState(ctx, client, cred, instance.ID, "RUNNING"); err != nil { t.Fatalf("wait instance running: %v", err) } return instance.ID } // cleanupAutoVCNIfAny 清理临时实例触发自动建网留下的 auto-vcn。 func cleanupAutoVCNIfAny(t *testing.T, client *RealClient, cred Credentials) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) defer cancel() vcns, err := client.ListVCNs(ctx, cred, "") if err != nil { return } for _, v := range vcns { if v.DisplayName == autoVcnName { cleanupVCN(t, client, cred, v.ID) } } }