Scert/backend/internal/repository/server_repository_test.go
xuans 5af752b25a 记录SSH私钥安全整改中间状态
提交密码认证收敛、历史字段清理迁移及相关测试调整,后续继续修复测试并完成复审。
2026-10-02 05:03:31 +08:00

176 lines
6.8 KiB
Go

package repository
import (
"context"
"path/filepath"
"testing"
"git.pcux.cn/xuans/Scert/backend/internal/config"
"git.pcux.cn/xuans/Scert/backend/internal/database"
"git.pcux.cn/xuans/Scert/backend/internal/deployment"
"git.pcux.cn/xuans/Scert/backend/internal/domain"
)
func TestServerRepositoryCRUDAndDomainAssociations(t *testing.T) {
dir := t.TempDir()
cfg := config.Default(dir).Database
cfg.DSN = filepath.Join(dir, "data", "servers.db")
db, err := database.Open(context.Background(), cfg)
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := database.Migrate(context.Background(), db.DB, "sqlite"); err != nil {
t.Fatal(err)
}
repo := NewSQLServerRepository(db.DB)
server := deployment.Server{
ID: "server-1", Name: "生产服务器", Address: "192.0.2.10", Port: 22, Username: "deploy",
AuthMethod: deployment.SSHAuthPassword, PasswordRef: "secret://ssh/password/server-1", HostKeyFingerprint: "SHA256:test",
CertificatePath: "/etc/scert/cert.pem", PrivateKeyPath: "/etc/scert/key.pem",
CertificateChainPath: "/etc/scert/fullchain.pem", WebServer: deployment.WebServerNginx,
}
if err := repo.CreateServer(context.Background(), server); err != nil {
t.Fatalf("创建服务器失败:%v", err)
}
loaded, err := repo.GetServerByID(context.Background(), server.ID)
if err != nil {
t.Fatalf("读取服务器失败:%v", err)
}
if loaded.Name != server.Name || loaded.ValidationCommand[0] != "nginx" || loaded.ValidationCommand[1] != "-t" || loaded.ReloadCommand[0] != "nginx" || loaded.CreatedAt.IsZero() {
t.Fatalf("服务器内容错误:%+v", loaded)
}
server.Name = "生产服务器-更新"
server.WebServer = deployment.WebServerCaddy
server.ReloadCommand = nil
if err := repo.UpdateServer(context.Background(), server); err != nil {
t.Fatalf("更新服务器失败:%v", err)
}
updated, err := repo.GetServerByID(context.Background(), server.ID)
if err != nil {
t.Fatalf("load updated server: %v", err)
}
if updated.CreatedAt.IsZero() || updated.UpdatedAt.IsZero() || updated.UpdatedAt.Before(updated.CreatedAt) {
t.Fatalf("updated server timestamps are invalid: %+v", updated)
}
item, err := domain.NewDomain("example.test", domain.CertificateSingle, nil)
if err != nil {
t.Fatal(err)
}
item.ID = "domain-1"
if err := NewSQLDomainRepository(db.DB).Create(t.Context(), item); err != nil {
t.Fatal(err)
}
if err := repo.ReplaceDomainServers(context.Background(), "domain-1", []string{server.ID, server.ID}); err != nil {
t.Fatalf("保存域名服务器关联失败:%v", err)
}
associated, err := repo.ListServersByDomainID(context.Background(), "domain-1")
if err != nil || len(associated) != 1 || associated[0].WebServer != deployment.WebServerCaddy {
t.Fatalf("域名服务器关联错误:%v %+v", err, associated)
}
servers, err := repo.ListServers(context.Background())
if err != nil || len(servers) != 1 {
t.Fatalf("服务器列表错误:%v %+v", err, servers)
}
if err := repo.DeleteServer(context.Background(), server.ID); err != nil {
t.Fatalf("删除服务器失败:%v", err)
}
associated, err = repo.ListServersByDomainID(context.Background(), "domain-1")
if err != nil {
t.Fatalf("读取删除后的域名服务器关联失败:%v", err)
}
if len(associated) != 0 {
t.Fatalf("删除服务器后不应保留悬空关联:%+v", associated)
}
}
func TestServerRepositoryRejectsInvalidServer(t *testing.T) {
db, err := database.Open(context.Background(), config.DatabaseConfig{
Driver: "sqlite", DSN: ":memory:", MaxOpenConns: 1, MaxIdleConns: 1,
})
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := database.Migrate(context.Background(), db.DB, "sqlite"); err != nil {
t.Fatal(err)
}
if err := NewSQLServerRepository(db.DB).CreateServer(context.Background(), deployment.Server{}); err == nil {
t.Fatal("无效服务器配置应被拒绝")
}
}
func TestServerRepositoryRejectsUnsupportedAuthentication(t *testing.T) {
db := openServerTestDatabase(t)
defer db.Close()
repo := NewSQLServerRepository(db.DB)
server := validTestServer()
server.ID = "server-private-key-rejected"
server.AuthMethod = deployment.SSHAuthMethod("unsupported")
if err := repo.CreateServer(t.Context(), server); err == nil {
t.Fatal("server repository must reject unsupported authentication")
}
}
func TestReplaceDomainServersRejectsUnknownServerWithoutChangingAssociations(t *testing.T) {
dir := t.TempDir()
cfg := config.Default(dir).Database
cfg.DSN = filepath.Join(dir, "data", "association.db")
db, err := database.Open(context.Background(), cfg)
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := database.Migrate(context.Background(), db.DB, "sqlite"); err != nil {
t.Fatal(err)
}
repo := NewSQLServerRepository(db.DB)
server := deployment.Server{ID: "server-1", Name: "服务器", Address: "192.0.2.20", Username: "deploy", AuthMethod: deployment.SSHAuthPassword, PasswordRef: "secret://ssh/password/1", HostKeyFingerprint: "SHA256:test", CertificatePath: "/etc/scert/cert.pem", PrivateKeyPath: "/etc/scert/key.pem", CertificateChainPath: "/etc/scert/fullchain.pem", WebServer: deployment.WebServerNginx}
if err := repo.CreateServer(context.Background(), server); err != nil {
t.Fatal(err)
}
item, err := domain.NewDomain("association.example.test", domain.CertificateSingle, nil)
if err != nil {
t.Fatal(err)
}
item.ID = "domain-1"
if err := NewSQLDomainRepository(db.DB).Create(t.Context(), item); err != nil {
t.Fatal(err)
}
if err := repo.ReplaceDomainServers(context.Background(), "domain-1", []string{server.ID}); err != nil {
t.Fatal(err)
}
if err := repo.ReplaceDomainServers(context.Background(), "domain-1", []string{"missing"}); err == nil {
t.Fatal("未知服务器应被拒绝")
}
items, err := repo.ListServersByDomainID(context.Background(), "domain-1")
if err != nil || len(items) != 1 || items[0].ID != server.ID {
t.Fatalf("失败后原关联应保持不变:%v %+v", err, items)
}
}
func openServerTestDatabase(t *testing.T) *database.Database {
t.Helper()
directory := t.TempDir()
cfg := config.Default(directory).Database
cfg.DSN = filepath.Join(directory, "data", "server-test.db")
db, err := database.Open(t.Context(), cfg)
if err != nil {
t.Fatalf("打开服务器测试数据库失败:%v", err)
}
if err := database.Migrate(t.Context(), db.DB, "sqlite"); err != nil {
_ = db.Close()
t.Fatalf("迁移服务器测试数据库失败:%v", err)
}
return db
}
func validTestServer() deployment.Server {
return deployment.Server{
Name: "测试服务器", Address: "192.0.2.70", Port: 22, Username: "deploy",
AuthMethod: deployment.SSHAuthPassword, PasswordRef: "secret://ssh/password/test", HostKeyFingerprint: "SHA256:test",
CertificatePath: "/etc/scert/cert.pem", PrivateKeyPath: "/etc/scert/key.pem",
CertificateChainPath: "/etc/scert/fullchain.pem", WebServer: deployment.WebServerNginx,
}
}