feat(ops): add object storage management and site-mapping CSV import/export
Ship the Ops backstage 对象存储 page (browse / upload / move / delete / folder ops) backed by a new object-storage service + handler, with the shared storage client extended with List/Stat/Copy/Usage/DownloadURL so both MinIO and Aliyun providers cover the new surface. Add ForcePathStyle to the object-storage config and treat r2/s3/custom_s3 as external providers so Cloudflare R2 and other S3-compatibles can share the MinIO client path without spinning up the bundled MinIO. Also add CSV import/export + template download to the site-domain mappings page, raise gin MaxMultipartMemory to 8 MiB for the new multipart endpoints, register Ops swagger routes, and extend the descriptions-parity test to cover the ops router. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
package app
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSiteDomainMappingCSVColumnsAcceptChineseHeaders(t *testing.T) {
|
||||
columns, err := detectSiteDomainMappingImportColumns([]string{"匹配域名", "站点 Key", "中文名", "状态"})
|
||||
if err != nil {
|
||||
t.Fatalf("detectSiteDomainMappingImportColumns() error = %v", err)
|
||||
}
|
||||
if columns.Domain != 0 || columns.Key != 1 || columns.Name != 2 || columns.Active != 3 {
|
||||
t.Fatalf("columns = %+v, want domain/key/name/active indexes 0/1/2/3", columns)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiteDomainMappingCSVColumnsRequireDomainAndName(t *testing.T) {
|
||||
if _, err := detectSiteDomainMappingImportColumns([]string{"site_key", "is_active"}); err == nil {
|
||||
t.Fatal("detectSiteDomainMappingImportColumns() error = nil, want error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSiteDomainMappingInputFromCSVRecordDefaultsAndParsesActive(t *testing.T) {
|
||||
columns := siteDomainMappingImportColumns{
|
||||
Domain: 0,
|
||||
Key: 1,
|
||||
Name: 2,
|
||||
Active: 3,
|
||||
}
|
||||
input, err := siteDomainMappingInputFromCSVRecord([]string{"https://www.example.com/path", "", "示例", "停用"}, columns)
|
||||
if err != nil {
|
||||
t.Fatalf("siteDomainMappingInputFromCSVRecord() error = %v", err)
|
||||
}
|
||||
normalized, err := normalizeSiteDomainMappingInput(input)
|
||||
if err != nil {
|
||||
t.Fatalf("normalizeSiteDomainMappingInput() error = %v", err)
|
||||
}
|
||||
if normalized.RegistrableDomain != "www.example.com" {
|
||||
t.Fatalf("RegistrableDomain = %q, want www.example.com", normalized.RegistrableDomain)
|
||||
}
|
||||
if normalized.SiteKey != "www.example.com" {
|
||||
t.Fatalf("SiteKey = %q, want default domain", normalized.SiteKey)
|
||||
}
|
||||
if normalized.SiteName != "示例" {
|
||||
t.Fatalf("SiteName = %q, want 示例", normalized.SiteName)
|
||||
}
|
||||
if normalized.IsActive {
|
||||
t.Fatal("IsActive = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseSiteDomainMappingActiveRejectsUnknownStatus(t *testing.T) {
|
||||
if _, err := parseSiteDomainMappingActive("待定"); err == nil {
|
||||
t.Fatal("parseSiteDomainMappingActive() error = nil, want error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user