server: 平台抽象层+bilibili、服务端加密凭据库、角色中间件;web: 精简页面/路由、macOS 客户端(Swift)与多份方案文档;移除误入库的编译产物

This commit is contained in:
Qiufeng
2026-08-21 10:53:32 +08:00
parent 0daa9782c9
commit 3585c39bab
103 changed files with 5842 additions and 3197 deletions
+61 -17
View File
@@ -2,8 +2,6 @@ package api_test
import (
"bytes"
"crypto/ed25519"
"crypto/rand"
"encoding/json"
"fmt"
"net/http/httptest"
@@ -36,33 +34,33 @@ func TestMain(m *testing.M) {
adm, err := gorm.Open(gormmysql.Open(adminDSN), &gorm.Config{})
if err != nil {
fmt.Println("skip: mysql admin connect failed:", err)
os.Exit(0)
os.Exit(1)
}
if err = adm.Exec("CREATE DATABASE IF NOT EXISTS everypublish_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci").Error; err != nil {
fmt.Println("skip: create test db failed:", err)
os.Exit(0)
os.Exit(1)
}
testDSN := "root:everypublish@tcp(127.0.0.1:3306)/everypublish_test?charset=utf8mb4&parseTime=True&loc=Local"
testDB, err = gorm.Open(gormmysql.Open(testDSN), &gorm.Config{})
if err != nil {
fmt.Println("skip: test db connect failed:", err)
os.Exit(0)
os.Exit(1)
}
resetTestDB()
cfg := &config.Config{
ServerAddr: ":0",
MySQLDSN: testDSN,
RedisAddr: "127.0.0.1:6379",
JWTSecret: "test-secret",
BaseURL: "http://127.0.0.1:8090",
StorageDir: "/tmp/everypublish-test",
ServerAddr: ":0",
MySQLDSN: testDSN,
RedisAddr: "127.0.0.1:6379",
JWTSecret: "test-secret",
BaseURL: "http://127.0.0.1:8090",
StorageDir: "/tmp/everypublish-test",
ExecutorMode: "mock",
}
rds := cache.New(cfg)
testHub = ws.NewHub()
dsp := ws.NewDispatcher(testDB, testHub, cfg)
_, serverKey, _ := ed25519.GenerateKey(rand.Reader)
testRouter = api.Router(cfg, testDB, rds, testHub, serverKey, dsp)
testRouter = api.Router(cfg, testDB, rds, testHub, dsp)
code := m.Run()
os.Exit(code)
}
@@ -73,8 +71,8 @@ func resetTestDB() {
testHub.Clear()
}
for _, t := range []interface{}{
&models.TransferToken{}, &models.PairingCode{}, &models.Notification{}, &models.AuditLog{},
&models.Challenge{}, &models.AgentDevice{}, &models.Task{}, &models.Material{},
&models.TransferToken{}, &models.Notification{}, &models.AuditLog{},
&models.Challenge{}, &models.AgentDevice{}, &models.PairingCode{}, &models.TaskEvent{}, &models.Task{}, &models.Material{},
&models.Account{}, &models.Member{}, &models.Workspace{}, &models.User{},
} {
testDB.Migrator().DropTable(t)
@@ -174,6 +172,24 @@ func TestWorkspaceAndMemberFlow(t *testing.T) {
if code != 200 || env.Code != 0 {
t.Fatalf("create ws failed: %d %s", code, env.Message)
}
var secondWS models.Workspace
_ = json.Unmarshal(env.Data, &secondWS)
code, _ = doReq(t, "PUT", fmt.Sprintf("/api/v1/workspaces/%d", secondWS.ID), aAccess, gin.H{"name": "越权空间"})
if code != 404 {
t.Fatalf("cross-workspace update should be 404, got %d", code)
}
var ownWorkspaces struct {
List []models.Workspace `json:"list"`
}
code, env = doReq(t, "GET", "/api/v1/workspaces", aAccess, nil)
if code != 200 || env.Code != 0 {
t.Fatalf("list own workspaces failed: %d %s", code, env.Message)
}
_ = json.Unmarshal(env.Data, &ownWorkspaces)
if len(ownWorkspaces.List) == 0 {
t.Fatal("owner should have at least one workspace")
}
primaryWSID := ownWorkspaces.List[0].ID
// A 邀请 B(viewer)
code, env = doReq(t, "POST", "/api/v1/members/invite", aAccess, gin.H{"email": "worker@test.com", "role": "viewer"})
if code != 200 || env.Code != 0 {
@@ -192,6 +208,17 @@ func TestWorkspaceAndMemberFlow(t *testing.T) {
if code != 200 || env.Code != 0 {
t.Fatalf("join failed: %d %s", code, env.Message)
}
code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/auth/switch-workspace/%d", primaryWSID), bAccess, nil)
if code != 200 || env.Code != 0 {
t.Fatalf("workspace switch failed: %d %s", code, env.Message)
}
var switched tokens
_ = json.Unmarshal(env.Data, &switched)
bAccess = switched.AccessToken
code, _ = doReq(t, "POST", "/api/v1/accounts", bAccess, gin.H{"platform": "douyin", "remark": "viewer-forbidden"})
if code != 403 {
t.Fatalf("viewer account write should be 403, got %d", code)
}
// A 查成员:2 人
code, env = doReq(t, "GET", "/api/v1/members", aAccess, nil)
if code != 200 || env.Code != 0 {
@@ -219,6 +246,23 @@ func TestWorkspaceAndMemberFlow(t *testing.T) {
if code != 200 || env.Code != 0 {
t.Fatalf("update role failed: %d %s", code, env.Message)
}
code, env = doReq(t, "POST", fmt.Sprintf("/api/v1/auth/switch-workspace/%d", primaryWSID), bAccess, nil)
if code != 200 || env.Code != 0 {
t.Fatalf("workspace switch after role update failed: %d %s", code, env.Message)
}
_ = json.Unmarshal(env.Data, &switched)
var switchInfo struct {
MemberRole string `json:"memberRole"`
}
_ = json.Unmarshal(env.Data, &switchInfo)
if switchInfo.MemberRole != "operator" {
t.Fatalf("workspace switch should reflect updated role, got %q", switchInfo.MemberRole)
}
bAccess = switched.AccessToken
code, env = doReq(t, "POST", "/api/v1/accounts", bAccess, gin.H{"platform": "douyin", "remark": "operator-allowed"})
if code != 200 || env.Code != 0 {
t.Fatalf("operator account write should be allowed: %d %s", code, env.Message)
}
// owner 不可被移除
code, _ = doReq(t, "DELETE", fmt.Sprintf("/api/v1/members/%d", ownerMember), aAccess, nil)
if code != 403 {
@@ -240,8 +284,8 @@ func TestWorkspaceAndMemberFlow(t *testing.T) {
t.Fatalf("audit list failed: %d %s", code, env.Message)
}
var al struct {
List []models.AuditLog `json:"list"`
Total int64 `json:"total"`
List []models.AuditLog `json:"list"`
Total int64 `json:"total"`
}
_ = json.Unmarshal(env.Data, &al)
if al.Total < 3 {