package oss import ( "bytes" "io" "net/http" "testing" "time" aliyunoss "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/tinode/chat/server/store" "github.com/tinode/chat/server/store/types" ) type fakeThirdPartyConfig map[string]string func (f fakeThirdPartyConfig) Get(group string, keys ...string) (map[string]string, error) { result := map[string]string{} for _, key := range keys { if value, ok := f[key]; ok { result[key] = value } } return result, nil } func TestTenantObjectKey(t *testing.T) { got, err := tenantObjectKey(types.TenantID(42), "abc123", time.Date(2026, 8, 11, 9, 0, 0, 0, time.UTC)) if err != nil { t.Fatalf("tenantObjectKey failed: %v", err) } if want := "tenants/42/2026/08/abc123"; got != want { t.Fatalf("tenantObjectKey = %q, want %q", got, want) } } func TestTenantObjectKeyRejectsZeroTenant(t *testing.T) { if _, err := tenantObjectKey(types.ZeroTenantID, "abc123", time.Time{}); err != types.ErrMalformed { t.Fatalf("tenantObjectKey err = %v, want malformed", err) } } func TestGetIdFromUrl(t *testing.T) { handler := ossHandler{conf: ossConfig{ServeURL: "/v0/file/s/"}} fid := types.Uid(123456) got := handler.GetIdFromUrl("/v0/file/s/" + fid.String() + ".jpg") if got != fid { t.Fatalf("GetIdFromUrl = %v, want %v", got, fid) } } func TestIsOSSObjectNotFound(t *testing.T) { if !isOSSObjectNotFound(aliyunoss.ServiceError{StatusCode: http.StatusNotFound, Code: "NoSuchKey"}) { t.Fatal("NoSuchKey should be treated as missing object") } if isOSSObjectNotFound(aliyunoss.ServiceError{StatusCode: http.StatusForbidden, Code: "AccessDenied"}) { t.Fatal("AccessDenied must not be treated as missing object") } } func TestOSSReadSeekCloser(t *testing.T) { data := []byte("0123456789") reader := &ossReadSeekCloser{ size: int64(len(data)), open: func(offset int64) (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(data[offset:])), nil }, } defer reader.Close() buf := make([]byte, 3) if n, err := reader.Read(buf); err != nil || n != 3 || string(buf) != "012" { t.Fatalf("initial read = %q, %d, %v", buf, n, err) } if pos, err := reader.Seek(-2, io.SeekEnd); err != nil || pos != 8 { t.Fatalf("seek end = %d, %v", pos, err) } if n, err := reader.Read(buf); err != nil && err != io.EOF || n != 2 || string(buf[:n]) != "89" { t.Fatalf("ranged read = %q, %d, %v", buf[:n], n, err) } if pos, err := reader.Seek(4, io.SeekStart); err != nil || pos != 4 { t.Fatalf("seek start = %d, %v", pos, err) } if n, err := reader.Read(buf[:2]); err != nil || n != 2 || string(buf[:2]) != "45" { t.Fatalf("second ranged read = %q, %d, %v", buf[:2], n, err) } } func TestLoadSysConfig(t *testing.T) { original := store.ThirdPartyConfig store.ThirdPartyConfig = fakeThirdPartyConfig{ "oss_enabled": "1", "oss_provider": "oss", "oss_endpoint": "oss-cn-shenzhen.aliyuncs.com", "oss_bucket": "im-files", "oss_region_id": "cn-shenzhen", "oss_access_key_id": "ak", "oss_access_key_secret": "sk", "oss_presign_ttl": "300", "oss_serve_url": "/files/", "oss_cache_control": "private", "oss_cors_origins": `["https://example.com"]`, "oss_verify_bucket": "true", } defer func() { store.ThirdPartyConfig = original }() handler := ossHandler{conf: ossConfig{UseSysConfig: true}} if err := handler.loadSysConfig(); err != nil { t.Fatalf("loadSysConfig failed: %v", err) } if handler.conf.Endpoint != "oss-cn-shenzhen.aliyuncs.com" || handler.conf.BucketName != "im-files" { t.Fatalf("unexpected OSS config: %+v", handler.conf) } if handler.conf.RegionID != "cn-shenzhen" || handler.conf.PresignTTL != 300 { t.Fatalf("unexpected OSS region or ttl: %+v", handler.conf) } if handler.conf.ServeURL != "/files/" || handler.conf.CacheControl != "private" || !handler.conf.VerifyBucket { t.Fatalf("unexpected OSS serve settings: %+v", handler.conf) } if len(handler.conf.CorsOrigins) != 1 || handler.conf.CorsOrigins[0] != "https://example.com" { t.Fatalf("unexpected OSS cors origins: %+v", handler.conf.CorsOrigins) } }