191 lines
6.7 KiB
Go
191 lines
6.7 KiB
Go
package repository
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"errors"
|
||
"path/filepath"
|
||
"strings"
|
||
"testing"
|
||
"time"
|
||
|
||
"git.pcux.cn/xuans/Scert/backend/internal/config"
|
||
"git.pcux.cn/xuans/Scert/backend/internal/database"
|
||
"git.pcux.cn/xuans/Scert/backend/internal/domain"
|
||
"git.pcux.cn/xuans/Scert/backend/internal/testsupport/mysqltest"
|
||
)
|
||
|
||
func TestSQLProjectRepositorySQLite(t *testing.T) {
|
||
cfg := config.Default(t.TempDir()).Database
|
||
cfg.DSN = filepath.Join(t.TempDir(), "projects.db")
|
||
db, err := database.Open(t.Context(), cfg)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
t.Cleanup(func() { _ = db.Close() })
|
||
if err := database.Migrate(t.Context(), db.DB, cfg.Driver); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
checkProjectRepository(t, NewSQLProjectRepository(db.DB))
|
||
if err := db.Close(); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
db, err = database.Open(t.Context(), cfg)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := database.Migrate(t.Context(), db.DB, cfg.Driver); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
items, err := NewSQLProjectRepository(db.DB).ListProjects(t.Context())
|
||
if err != nil || len(items) != 2 || items[0].Name != "调整后的默认项目" || items[1].ID != "retained" {
|
||
t.Fatalf("关闭重开后项目未保留:%+v,%v", items, err)
|
||
}
|
||
}
|
||
|
||
func TestSQLProjectRepositoryMySQLReal(t *testing.T) {
|
||
server := mysqltest.New(t)
|
||
cfg := server.Database(t)
|
||
db, err := database.Open(t.Context(), cfg)
|
||
if err != nil {
|
||
t.Fatal("打开独立 MySQL 项目测试库失败")
|
||
}
|
||
t.Cleanup(func() { _ = db.Close() })
|
||
if err := database.Migrate(t.Context(), db.DB, cfg.Driver); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
checkProjectRepository(t, NewSQLProjectRepository(db.DB, cfg.Driver))
|
||
}
|
||
|
||
func checkProjectRepository(t *testing.T, repo *SQLProjectRepository) {
|
||
t.Helper()
|
||
ctx := t.Context()
|
||
items, err := repo.ListProjects(ctx)
|
||
if err != nil || len(items) != 1 || items[0].ID != domain.DefaultProjectID || items[0].Name != "默认项目" {
|
||
t.Fatalf("默认项目缺失:%+v,%v", items, err)
|
||
}
|
||
initial := items[0]
|
||
t.Run("reserved_default", func(t *testing.T) {
|
||
if err := repo.CreateProject(ctx, initial); !errors.Is(err, ErrDefaultProject) {
|
||
t.Fatalf("未拒绝重复创建默认项目:%v", err)
|
||
}
|
||
if err := repo.DeleteProject(ctx, " default "); !errors.Is(err, ErrDefaultProject) {
|
||
t.Fatalf("未阻止删除默认项目:%v", err)
|
||
}
|
||
})
|
||
item := domain.Project{ID: "project-a", Name: " 证书项目🔒 ", Description: " 项目说明\n第二行 "}
|
||
item.CreatedAt = time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||
t.Run("create_and_read", func(t *testing.T) {
|
||
if err := repo.CreateProject(ctx, item); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
loaded, err := repo.GetProject(ctx, " project-a ")
|
||
if err != nil || loaded.Name != strings.TrimSpace(item.Name) || loaded.Description != strings.TrimSpace(item.Description) {
|
||
t.Fatalf("项目数据读取不符:%+v,%v", loaded, err)
|
||
}
|
||
if loaded.CreatedAt.Equal(item.CreatedAt) || loaded.CreatedAt.IsZero() || !loaded.CreatedAt.Equal(loaded.UpdatedAt) {
|
||
t.Fatal("仓储必须生成自己的创建和更新时间")
|
||
}
|
||
item = loaded
|
||
})
|
||
t.Run("duplicate_and_invalid", func(t *testing.T) {
|
||
if err := repo.CreateProject(ctx, item); err == nil {
|
||
t.Fatal("重复项目主键未拒绝")
|
||
}
|
||
bad := item
|
||
bad.Name = " "
|
||
if err := repo.CreateProject(ctx, bad); err == nil {
|
||
t.Fatal("创建未校验项目")
|
||
}
|
||
if err := repo.UpdateProject(ctx, bad); err == nil {
|
||
t.Fatal("更新未校验项目")
|
||
}
|
||
})
|
||
t.Run("update_and_order", func(t *testing.T) {
|
||
before, err := repo.GetProject(ctx, item.ID)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
item.Name = "更新后的项目"
|
||
item.CreatedAt = time.Time{}
|
||
for round := 0; round < 2; round++ {
|
||
if err := repo.UpdateProject(ctx, item); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
}
|
||
loaded, err := repo.GetProject(ctx, item.ID)
|
||
if err != nil || loaded.Name != item.Name || !loaded.CreatedAt.Equal(before.CreatedAt) || loaded.UpdatedAt.Before(before.UpdatedAt) {
|
||
t.Fatalf("更新改变了创建时间或丢失内容:%+v,%v", loaded, err)
|
||
}
|
||
if err := repo.CreateProject(ctx, domain.Project{ID: "retained", Name: item.Name}); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
items, err := repo.ListProjects(ctx)
|
||
if err != nil || len(items) != 3 || items[0].ID != domain.DefaultProjectID {
|
||
t.Fatalf("项目列表必须默认项目在前:%+v,%v", items, err)
|
||
}
|
||
initial.Name = "调整后的默认项目"
|
||
if err := repo.UpdateProject(ctx, initial); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
})
|
||
t.Run("cancelled_context", func(t *testing.T) {
|
||
cancelled, cancel := context.WithCancel(ctx)
|
||
cancel()
|
||
if err := repo.CreateProject(cancelled, domain.Project{ID: "cancelled", Name: "不应写入"}); !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("取消后创建未被拒绝:%v", err)
|
||
}
|
||
if _, err := repo.ListProjects(cancelled); !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("取消后查询未被拒绝:%v", err)
|
||
}
|
||
if err := repo.DeleteProject(cancelled, item.ID); !errors.Is(err, context.Canceled) {
|
||
t.Fatalf("取消后删除未被拒绝:%v", err)
|
||
}
|
||
})
|
||
t.Run("missing_and_injection", func(t *testing.T) {
|
||
for _, id := range []string{"missing", "' OR 1=1 --"} {
|
||
if _, err := repo.GetProject(ctx, id); !errors.Is(err, sql.ErrNoRows) {
|
||
t.Fatalf("查询不存在项目应保留未找到语义:%v", err)
|
||
}
|
||
if err := repo.DeleteProject(ctx, id); !errors.Is(err, sql.ErrNoRows) {
|
||
t.Fatalf("删除不存在项目应保留未找到语义:%v", err)
|
||
}
|
||
}
|
||
if err := repo.UpdateProject(ctx, domain.Project{ID: "missing", Name: "不存在"}); !errors.Is(err, sql.ErrNoRows) {
|
||
t.Fatalf("更新不存在项目应保留未找到语义:%v", err)
|
||
}
|
||
})
|
||
t.Run("delete", func(t *testing.T) {
|
||
if err := repo.DeleteProject(ctx, item.ID); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if _, err := repo.GetProject(ctx, item.ID); !errors.Is(err, sql.ErrNoRows) {
|
||
t.Fatalf("删除后仍能读取项目:%v", err)
|
||
}
|
||
items, err := repo.ListProjects(ctx)
|
||
if err != nil || len(items) != 2 || items[1].ID != "retained" {
|
||
t.Fatalf("删除项目影响了其他项目:%+v,%v", items, err)
|
||
}
|
||
})
|
||
}
|
||
|
||
func TestSQLProjectRepositoryNilDatabase(t *testing.T) {
|
||
for _, repo := range []*SQLProjectRepository{nil, NewSQLProjectRepository(nil)} {
|
||
if err := repo.CreateProject(t.Context(), domain.Project{}); err == nil {
|
||
t.Fatal("创建没有拒绝空数据库")
|
||
}
|
||
if _, err := repo.GetProject(t.Context(), "a"); err == nil {
|
||
t.Fatal("查询没有拒绝空数据库")
|
||
}
|
||
if _, err := repo.ListProjects(t.Context()); err == nil {
|
||
t.Fatal("列表没有拒绝空数据库")
|
||
}
|
||
if err := repo.UpdateProject(t.Context(), domain.Project{}); err == nil {
|
||
t.Fatal("更新没有拒绝空数据库")
|
||
}
|
||
if err := repo.DeleteProject(t.Context(), "a"); err == nil {
|
||
t.Fatal("删除没有拒绝空数据库")
|
||
}
|
||
}
|
||
}
|