Scert/backend/internal/repository/project_repository_test.go

191 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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("删除没有拒绝空数据库")
}
}
}