@@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"oci-portal/internal/model"
|
||||
"oci-portal/internal/oci"
|
||||
)
|
||||
|
||||
@@ -27,10 +28,19 @@ type osStubClient struct {
|
||||
putIfMatch string
|
||||
|
||||
// 后台清空删除桶的编排状态,goroutine 并发访问需加锁
|
||||
mu sync.Mutex
|
||||
versions []oci.ObjectVersion
|
||||
pars []oci.PAR
|
||||
bucketDeleted bool
|
||||
mu sync.Mutex
|
||||
versions []oci.ObjectVersion
|
||||
pars []oci.PAR
|
||||
bucketDeleted bool
|
||||
multipartAborted bool
|
||||
listCalls int
|
||||
}
|
||||
|
||||
func (f *osStubClient) AbortAllMultipartUploads(ctx context.Context, cred oci.Credentials, region, bucket string) error {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.multipartAborted = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *osStubClient) ListBuckets(ctx context.Context, cred oci.Credentials, region, compartmentID string) ([]oci.Bucket, error) {
|
||||
@@ -62,6 +72,7 @@ func (f *osStubClient) DeleteBucket(ctx context.Context, cred oci.Credentials, r
|
||||
func (f *osStubClient) ListObjectVersions(ctx context.Context, cred oci.Credentials, region, bucket, page string) ([]oci.ObjectVersion, string, error) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.listCalls++
|
||||
return append([]oci.ObjectVersion(nil), f.versions...), "", nil
|
||||
}
|
||||
|
||||
@@ -170,10 +181,20 @@ func TestObjectStorageValidation(t *testing.T) {
|
||||
_, err := svc.CreatePAR(ctx, cfg.ID, "r", "b", oci.CreatePARInput{AccessType: "Whatever", ExpiresHours: 2})
|
||||
return err
|
||||
}, wantErr: "unsupported access type"},
|
||||
{name: "PAR 时长越界", run: func() error {
|
||||
_, err := svc.CreatePAR(ctx, cfg.ID, "r", "b", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: 800})
|
||||
{name: "PAR 时长过短", run: func() error {
|
||||
_, err := svc.CreatePAR(ctx, cfg.ID, "r", "b", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: 0})
|
||||
return err
|
||||
}, wantErr: "1-720"},
|
||||
}, wantErr: "1-876000"},
|
||||
// 旧 30 天上限已放开:10 年应放行
|
||||
{name: "PAR 超长有效期放行", run: func() error {
|
||||
_, err := svc.CreatePAR(ctx, cfg.ID, "r", "b", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: 24 * 3650})
|
||||
return err
|
||||
}},
|
||||
// 防 time.Duration 溢出的安全上限(100 年)仍要拦截
|
||||
{name: "PAR 超安全上限拒绝", run: func() error {
|
||||
_, err := svc.CreatePAR(ctx, cfg.ID, "r", "b", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: maxParHours + 1})
|
||||
return err
|
||||
}, wantErr: "1-876000"},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@@ -403,3 +424,118 @@ func TestCreatePARDefaultsName(t *testing.T) {
|
||||
t.Errorf("FullURL empty, want populated")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreatePARAccessTypes 桶级 AnyObject* 必须放行(分享桶 500 复盘),非法类型拒绝。
|
||||
func TestCreatePARAccessTypes(t *testing.T) {
|
||||
client := &osStubClient{fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}}}
|
||||
svc := newTestService(t, client)
|
||||
cfg := importAliveConfig(t, svc)
|
||||
|
||||
cases := []struct {
|
||||
accessType string
|
||||
wantErr bool
|
||||
}{
|
||||
{"AnyObjectRead", false}, {"AnyObjectWrite", false}, {"AnyObjectReadWrite", false},
|
||||
{"BucketRead", true}, {"", true},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
_, err := svc.CreatePAR(context.Background(), cfg.ID, "r", "b",
|
||||
oci.CreatePARInput{AccessType: tc.accessType, ExpiresHours: 24})
|
||||
if (err != nil) != tc.wantErr {
|
||||
t.Errorf("CreatePAR(%q) err = %v, wantErr %v", tc.accessType, err, tc.wantErr)
|
||||
}
|
||||
}
|
||||
if client.createdPAR.Name != "par-bucket-b" {
|
||||
t.Errorf("bucket par default name = %q, want par-bucket-b", client.createdPAR.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreatePARValidationSentinels(t *testing.T) {
|
||||
svc := &OciConfigService{}
|
||||
cases := []struct {
|
||||
name string
|
||||
in oci.CreatePARInput
|
||||
want error
|
||||
}{
|
||||
{"access type", oci.CreatePARInput{AccessType: "BucketRead", ExpiresHours: 1}, ErrPARInvalidAccessType},
|
||||
{"expiration low", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: 0}, ErrPARInvalidExpiration},
|
||||
{"expiration high", oci.CreatePARInput{AccessType: "ObjectRead", ExpiresHours: maxParHours + 1}, ErrPARInvalidExpiration},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
_, err := svc.CreatePAR(context.Background(), 1, "r", "b", tc.in)
|
||||
if !errors.Is(err, tc.want) {
|
||||
t.Errorf("%s err = %v, want errors.Is(%v)", tc.name, err, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestDeleteBucketPurgeSingleflight 锁定同桶清空单飞:任务在途时重复删除请求
|
||||
// 按已排队返回,不并发起第二个清空任务。
|
||||
func TestDeleteBucketPurgeSingleflight(t *testing.T) {
|
||||
client := &osStubClient{
|
||||
fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}},
|
||||
versions: []oci.ObjectVersion{{Name: "a.txt", VersionID: "v1"}},
|
||||
}
|
||||
svc := newTestService(t, client)
|
||||
cfg := importAliveConfig(t, svc)
|
||||
key := fmt.Sprintf("%d/r/b", cfg.ID)
|
||||
if !svc.beginPurge(key) {
|
||||
t.Fatal("beginPurge 首次抢占失败")
|
||||
}
|
||||
queued, err := svc.DeleteBucket(context.Background(), cfg.ID, "r", "b")
|
||||
if err != nil || !queued {
|
||||
t.Fatalf("在途时 DeleteBucket = queued %v, %v, want queued", queued, err)
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
client.mu.Lock()
|
||||
calls := client.listCalls
|
||||
client.mu.Unlock()
|
||||
if calls != 0 {
|
||||
t.Fatalf("在途期间起了新清空任务: listCalls=%d, want 0", calls)
|
||||
}
|
||||
svc.endPurge(key)
|
||||
if !svc.beginPurge(key) {
|
||||
t.Error("endPurge 后应可重新抢占")
|
||||
}
|
||||
svc.endPurge(key)
|
||||
}
|
||||
|
||||
// TestDeleteBucketPurgeAbortsMultipart 锁定清空顺序包含中止未完成分片上传。
|
||||
func TestDeleteBucketPurgeAbortsMultipart(t *testing.T) {
|
||||
client := &osStubClient{
|
||||
fakeClient: &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}},
|
||||
versions: []oci.ObjectVersion{{Name: "a.txt", VersionID: "v1"}},
|
||||
}
|
||||
svc := newTestService(t, client)
|
||||
cfg := importAliveConfig(t, svc)
|
||||
if queued, err := svc.DeleteBucket(context.Background(), cfg.ID, "r", "b"); err != nil || !queued {
|
||||
t.Fatalf("DeleteBucket = queued %v, %v, want queued", queued, err)
|
||||
}
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
client.mu.Lock()
|
||||
done := client.bucketDeleted && client.multipartAborted
|
||||
client.mu.Unlock()
|
||||
if done {
|
||||
return
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatal("purge 未在期限内完成分片中止与删桶")
|
||||
}
|
||||
|
||||
// TestImportRejectsMissingProxy 锁定导入防线:关联不存在的代理直接拒绝,
|
||||
// 不落库也不发起绕过代理的直连测活。
|
||||
func TestImportRejectsMissingProxy(t *testing.T) {
|
||||
svc := newTestService(t, &fakeClient{tenancy: oci.TenancyInfo{Name: "t"}})
|
||||
in := trialImportInput()
|
||||
missing := uint(999)
|
||||
in.ProxyID = &missing
|
||||
if _, err := svc.Import(context.Background(), in); err == nil {
|
||||
t.Fatal("Import 关联不存在的代理应报错")
|
||||
}
|
||||
var n int64
|
||||
if err := svc.db.Model(&model.OciConfig{}).Count(&n).Error; err != nil || n != 0 {
|
||||
t.Fatalf("拒绝导入后配置数 = %d (%v), want 0", n, err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user