初始提交:OCI 面板后端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:04 +08:00
commit b9a3e97e84
168 changed files with 31794 additions and 0 deletions
@@ -0,0 +1,265 @@
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)
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)
}
}
}