Files
MeBox/internal/service/reader/source_per_user_test.go
T
truewhile 42703c6760 优化
2026-10-10 12:00:33 +08:00

111 lines
3.6 KiB
Go

package reader
import (
"strings"
"testing"
)
// 书源按用户独立:同一 URL 的两个用户各自持有副本,启停/删除/搜索互不影响。
func TestSourcesArePerUser(t *testing.T) {
srv := newCacheTestServer()
defer srv.Close()
svc, _ := newLoginTestService(t)
ctx := t.Context()
raw := cacheTestSourceJSON(srv.URL)
if _, err := svc.ImportSources(ctx, "u1", raw); err != nil {
t.Fatalf("u1 导入失败: %v", err)
}
// 第二个用户导入同一书源 URL:旧实现会撞 source_url 唯一索引(导入失败)。
if _, err := svc.ImportSources(ctx, "u2", raw); err != nil {
t.Fatalf("u2 导入同一书源失败(书源未按用户隔离): %v", err)
}
u1Sources, err := svc.ListSources(ctx, "u1")
if err != nil || len(u1Sources) != 1 {
t.Fatalf("u1 应有 1 个书源: n=%d err=%v", len(u1Sources), err)
}
u2Sources, err := svc.ListSources(ctx, "u2")
if err != nil || len(u2Sources) != 1 {
t.Fatalf("u2 应有 1 个书源: n=%d err=%v", len(u2Sources), err)
}
if u1Sources[0].ID == u2Sources[0].ID {
t.Fatal("两个用户应各持一份副本(行 ID 不同)")
}
if u1Sources[0].UserID != "u1" || u2Sources[0].UserID != "u2" {
t.Fatalf("书源归属错误: u1=%q u2=%q", u1Sources[0].UserID, u2Sources[0].UserID)
}
// 启停互不影响。
if err := svc.UpdateSourceEnabled(ctx, "u1", u1Sources[0].ID, false); err != nil {
t.Fatalf("u1 停用失败: %v", err)
}
u2After, _ := svc.ListSources(ctx, "u2")
if len(u2After) != 1 || !u2After[0].Enabled {
t.Fatal("u1 停用影响到了 u2 的书源")
}
// 删除只删自己那份。
if err := svc.DeleteSource(ctx, "u1", u1Sources[0].ID); err != nil {
t.Fatalf("u1 删除失败: %v", err)
}
if left, _ := svc.ListSources(ctx, "u1"); len(left) != 0 {
t.Fatalf("u1 应已无书源: n=%d", len(left))
}
if left, _ := svc.ListSources(ctx, "u2"); len(left) != 1 {
t.Fatalf("u2 的书源被误删: n=%d", len(left))
}
// 越权删除他人书源必须失败。
if err := svc.DeleteSource(ctx, "u1", u2Sources[0].ID); err == nil {
t.Fatal("u1 不应能删除 u2 的书源")
}
}
// 搜索只使用当前用户的书源范围。
func TestSearchUsesOwnSourcesOnly(t *testing.T) {
srv := e2eServer()
defer srv.Close()
svc, _ := newLoginTestService(t)
ctx := t.Context()
if _, err := svc.ImportSources(ctx, "u1", e2eSourceJSON(srv.URL)); err != nil {
t.Fatal(err)
}
// u2 没有任何书源:搜索应直接报「没有已启用的书源」,而不是用 u1 的源。
if _, _, err := svc.Search(ctx, "u2", "书", nil, 1); err == nil || !strings.Contains(err.Error(), "没有已启用的书源") {
t.Fatalf("u2 无书源时应拒绝搜索,实际 err=%v", err)
}
// u1 自己的搜索正常。
books, _, err := svc.Search(ctx, "u1", "书", nil, 1)
if err != nil {
t.Fatalf("u1 搜索失败: %v", err)
}
if len(books) == 0 {
t.Fatal("u1 应搜到结果")
}
}
// 同一书源 URL 的两个用户副本:删除再导入仍然可用(物理删除释放唯一键)。
func TestDeleteAndReimportSameSource(t *testing.T) {
srv := newCacheTestServer()
defer srv.Close()
svc, _ := newLoginTestService(t)
ctx := t.Context()
if _, err := svc.ImportSources(ctx, "u1", cacheTestSourceJSON(srv.URL)); err != nil {
t.Fatal(err)
}
sources, _ := svc.ListSources(ctx, "u1")
if err := svc.DeleteSource(ctx, "u1", sources[0].ID); err != nil {
t.Fatalf("删除失败: %v", err)
}
if _, err := svc.ImportSources(ctx, "u1", cacheTestSourceJSON(srv.URL)); err != nil {
t.Fatalf("删除后重新导入失败: %v", err)
}
if again, _ := svc.ListSources(ctx, "u1"); len(again) != 1 {
t.Fatalf("重新导入后应有 1 个书源: n=%d", len(again))
}
}