mirror of
https://github.com/nianzhibai/91.git
synced 2026-06-15 08:45:41 +08:00
515 lines
15 KiB
Go
515 lines
15 KiB
Go
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/video-site/backend/internal/catalog"
|
|
)
|
|
|
|
func TestHandleUpsertDrivePreservesExistingCredentialsWhenRequestCredentialsEmpty(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
if err := cat.UpsertDrive(ctx, &catalog.Drive{
|
|
ID: "quark-main",
|
|
Kind: "quark",
|
|
Name: "Old name",
|
|
RootID: "0",
|
|
ScanRootID: "0",
|
|
Credentials: map[string]string{
|
|
"cookie": "existing-cookie",
|
|
},
|
|
Status: "ok",
|
|
}); err != nil {
|
|
t.Fatalf("seed drive: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/api/drives", strings.NewReader(`{
|
|
"id": "quark-main",
|
|
"kind": "quark",
|
|
"name": "New name",
|
|
"rootId": "0",
|
|
"scanRootId": "scan-root",
|
|
"credentials": {}
|
|
}`))
|
|
rr := httptest.NewRecorder()
|
|
|
|
(&AdminServer{Catalog: cat}).handleUpsertDrive(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
got, err := cat.GetDrive(ctx, "quark-main")
|
|
if err != nil {
|
|
t.Fatalf("get drive: %v", err)
|
|
}
|
|
if got.Name != "New name" {
|
|
t.Fatalf("name = %q, want New name", got.Name)
|
|
}
|
|
if got.ScanRootID != "scan-root" {
|
|
t.Fatalf("scanRootId = %q, want scan-root", got.ScanRootID)
|
|
}
|
|
if got.Credentials["cookie"] != "existing-cookie" {
|
|
t.Fatalf("cookie credential = %q, want existing-cookie", got.Credentials["cookie"])
|
|
}
|
|
}
|
|
|
|
func TestHandleUpsertDriveReplacesExistingCredentialsWhenProvided(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
if err := cat.UpsertDrive(ctx, &catalog.Drive{
|
|
ID: "quark-main",
|
|
Kind: "quark",
|
|
Name: "Old name",
|
|
RootID: "0",
|
|
ScanRootID: "0",
|
|
Credentials: map[string]string{
|
|
"cookie": "existing-cookie",
|
|
},
|
|
Status: "ok",
|
|
}); err != nil {
|
|
t.Fatalf("seed drive: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/api/drives", bytes.NewBufferString(`{
|
|
"id": "quark-main",
|
|
"kind": "quark",
|
|
"name": "New name",
|
|
"rootId": "0",
|
|
"scanRootId": "0",
|
|
"credentials": {"cookie": "new-cookie"}
|
|
}`))
|
|
rr := httptest.NewRecorder()
|
|
|
|
(&AdminServer{Catalog: cat}).handleUpsertDrive(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
got, err := cat.GetDrive(ctx, "quark-main")
|
|
if err != nil {
|
|
t.Fatalf("get drive: %v", err)
|
|
}
|
|
if got.Credentials["cookie"] != "new-cookie" {
|
|
t.Fatalf("cookie credential = %q, want new-cookie", got.Credentials["cookie"])
|
|
}
|
|
}
|
|
|
|
func TestHandleListDrivesIncludesTeaserCounts(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
for _, d := range []*catalog.Drive{
|
|
{ID: "OneDrive", Kind: "onedrive", Name: "OneDrive", RootID: "root", Status: "ok"},
|
|
{ID: "PikPak", Kind: "pikpak", Name: "PikPak", RootID: "", Status: "ok"},
|
|
} {
|
|
if err := cat.UpsertDrive(ctx, d); err != nil {
|
|
t.Fatalf("seed drive %s: %v", d.ID, err)
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
videos := []*catalog.Video{
|
|
{ID: "od-ready-1", DriveID: "OneDrive", FileID: "od-file-1", Title: "OD Ready 1", PreviewStatus: "ready", PublishedAt: now, CreatedAt: now, UpdatedAt: now},
|
|
{ID: "od-ready-2", DriveID: "OneDrive", FileID: "od-file-2", Title: "OD Ready 2", PreviewStatus: "ready", PublishedAt: now, CreatedAt: now, UpdatedAt: now},
|
|
{ID: "od-pending", DriveID: "OneDrive", FileID: "od-file-3", Title: "OD Pending", PreviewStatus: "pending", PublishedAt: now, CreatedAt: now, UpdatedAt: now},
|
|
{ID: "pp-pending", DriveID: "PikPak", FileID: "pp-file-1", Title: "PP Pending", PreviewStatus: "pending", PublishedAt: now, CreatedAt: now, UpdatedAt: now},
|
|
{ID: "pp-failed", DriveID: "PikPak", FileID: "pp-file-2", Title: "PP Failed", PreviewStatus: "failed", PublishedAt: now, CreatedAt: now, UpdatedAt: now},
|
|
}
|
|
for _, v := range videos {
|
|
if err := cat.UpsertVideo(ctx, v); err != nil {
|
|
t.Fatalf("seed video %s: %v", v.ID, err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/api/drives", nil)
|
|
rr := httptest.NewRecorder()
|
|
(&AdminServer{Catalog: cat}).handleListDrives(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
var got []struct {
|
|
ID string `json:"id"`
|
|
TeaserReadyCount int `json:"teaserReadyCount"`
|
|
TeaserPendingCount int `json:"teaserPendingCount"`
|
|
TeaserFailedCount int `json:"teaserFailedCount"`
|
|
}
|
|
if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
byID := map[string]struct {
|
|
Ready int
|
|
Pending int
|
|
Failed int
|
|
}{}
|
|
for _, d := range got {
|
|
byID[d.ID] = struct {
|
|
Ready int
|
|
Pending int
|
|
Failed int
|
|
}{Ready: d.TeaserReadyCount, Pending: d.TeaserPendingCount, Failed: d.TeaserFailedCount}
|
|
}
|
|
if byID["OneDrive"].Ready != 2 || byID["OneDrive"].Pending != 1 || byID["OneDrive"].Failed != 0 {
|
|
t.Fatalf("OneDrive counts = %#v, want ready=2 pending=1 failed=0", byID["OneDrive"])
|
|
}
|
|
if byID["PikPak"].Ready != 0 || byID["PikPak"].Pending != 1 || byID["PikPak"].Failed != 1 {
|
|
t.Fatalf("PikPak counts = %#v, want ready=0 pending=1 failed=1", byID["PikPak"])
|
|
}
|
|
}
|
|
|
|
func TestHandleDriveStorageReportsLocalMediaUsage(t *testing.T) {
|
|
ctx := context.Background()
|
|
root := t.TempDir()
|
|
cat, err := catalog.Open(filepath.Join(root, "catalog.db"))
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
localDir := filepath.Join(root, "previews")
|
|
thumbDir := filepath.Join(localDir, "thumbs")
|
|
if err := os.MkdirAll(thumbDir, 0o755); err != nil {
|
|
t.Fatalf("mkdir thumbs: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(localDir, "drive-one-video.mp4"), []byte("teaser-one"), 0o644); err != nil {
|
|
t.Fatalf("write teaser one: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(localDir, "drive-two-video.mp4"), []byte("teaser-two!!"), 0o644); err != nil {
|
|
t.Fatalf("write teaser two: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(thumbDir, "drive-one-video.jpg"), []byte("jpg-one"), 0o644); err != nil {
|
|
t.Fatalf("write thumb one: %v", err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(thumbDir, "drive-two-video.jpg"), []byte("jpg-two!!"), 0o644); err != nil {
|
|
t.Fatalf("write thumb two: %v", err)
|
|
}
|
|
|
|
for _, d := range []*catalog.Drive{
|
|
{ID: "drive-one", Kind: "onedrive", Name: "Drive One", RootID: "root", Status: "ok"},
|
|
{ID: "drive-two", Kind: "pikpak", Name: "Drive Two", RootID: "", Status: "ok"},
|
|
} {
|
|
if err := cat.UpsertDrive(ctx, d); err != nil {
|
|
t.Fatalf("seed drive %s: %v", d.ID, err)
|
|
}
|
|
}
|
|
now := time.Now()
|
|
for _, v := range []*catalog.Video{
|
|
{
|
|
ID: "drive-one-video",
|
|
DriveID: "drive-one",
|
|
FileID: "file-one",
|
|
Title: "Video One",
|
|
PreviewLocal: filepath.Join(localDir, "drive-one-video.mp4"),
|
|
PreviewStatus: "ready",
|
|
PublishedAt: now,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
{
|
|
ID: "drive-two-video",
|
|
DriveID: "drive-two",
|
|
FileID: "file-two",
|
|
Title: "Video Two",
|
|
PreviewLocal: filepath.Join(localDir, "drive-two-video.mp4"),
|
|
PreviewStatus: "ready",
|
|
PublishedAt: now,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
} {
|
|
if err := cat.UpsertVideo(ctx, v); err != nil {
|
|
t.Fatalf("seed video %s: %v", v.ID, err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/api/drives/storage", nil)
|
|
rr := httptest.NewRecorder()
|
|
(&AdminServer{Catalog: cat, LocalPreviewDir: localDir}).handleDriveStorage(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
var got struct {
|
|
ThumbnailBytes int64 `json:"thumbnailBytes"`
|
|
TeaserBytes int64 `json:"teaserBytes"`
|
|
TotalBytes int64 `json:"totalBytes"`
|
|
AvailableBytes int64 `json:"availableBytes"`
|
|
Drives map[string]struct {
|
|
ThumbnailBytes int64 `json:"thumbnailBytes"`
|
|
TeaserBytes int64 `json:"teaserBytes"`
|
|
TotalBytes int64 `json:"totalBytes"`
|
|
} `json:"drives"`
|
|
}
|
|
if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.ThumbnailBytes != int64(len("jpg-one")+len("jpg-two!!")) {
|
|
t.Fatalf("thumbnail bytes = %d, want %d", got.ThumbnailBytes, len("jpg-one")+len("jpg-two!!"))
|
|
}
|
|
if got.TeaserBytes != int64(len("teaser-one")+len("teaser-two!!")) {
|
|
t.Fatalf("teaser bytes = %d, want %d", got.TeaserBytes, len("teaser-one")+len("teaser-two!!"))
|
|
}
|
|
if got.TotalBytes != got.ThumbnailBytes+got.TeaserBytes {
|
|
t.Fatalf("total bytes = %d, want thumbnail + teaser", got.TotalBytes)
|
|
}
|
|
if got.AvailableBytes <= 0 {
|
|
t.Fatalf("available bytes = %d, want positive", got.AvailableBytes)
|
|
}
|
|
if got.Drives["drive-one"].ThumbnailBytes != int64(len("jpg-one")) ||
|
|
got.Drives["drive-one"].TeaserBytes != int64(len("teaser-one")) {
|
|
t.Fatalf("drive-one usage = %#v", got.Drives["drive-one"])
|
|
}
|
|
if got.Drives["drive-two"].TotalBytes != int64(len("jpg-two!!")+len("teaser-two!!")) {
|
|
t.Fatalf("drive-two total = %d, want %d", got.Drives["drive-two"].TotalBytes, len("jpg-two!!")+len("teaser-two!!"))
|
|
}
|
|
}
|
|
|
|
func TestHandleCreateTagClassifiesExistingVideos(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
now := time.Now()
|
|
if err := cat.UpsertVideo(ctx, &catalog.Video{
|
|
ID: "video-1",
|
|
DriveID: "drive",
|
|
FileID: "file-1",
|
|
Title: "清纯短发",
|
|
PublishedAt: now,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}); err != nil {
|
|
t.Fatalf("seed video: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/api/tags", strings.NewReader(`{"label":"清纯"}`))
|
|
rr := httptest.NewRecorder()
|
|
(&AdminServer{Catalog: cat}).handleCreateTag(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
var got struct {
|
|
Label string `json:"label"`
|
|
Classified int `json:"classified"`
|
|
}
|
|
if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.Label != "清纯" || got.Classified != 1 {
|
|
t.Fatalf("response = %#v, want 清纯 classified 1", got)
|
|
}
|
|
|
|
video, err := cat.GetVideo(ctx, "video-1")
|
|
if err != nil {
|
|
t.Fatalf("get video: %v", err)
|
|
}
|
|
if len(video.Tags) != 1 || video.Tags[0] != "清纯" {
|
|
t.Fatalf("video tags = %#v, want 清纯", video.Tags)
|
|
}
|
|
}
|
|
|
|
func TestHandleAdminListVideosFiltersByDriveID(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
now := time.Now()
|
|
videos := []*catalog.Video{
|
|
{
|
|
ID: "od-video",
|
|
DriveID: "OneDrive",
|
|
FileID: "od-file",
|
|
Title: "OneDrive video",
|
|
PublishedAt: now,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
{
|
|
ID: "pp-video",
|
|
DriveID: "PikPak",
|
|
FileID: "pp-file",
|
|
Title: "PikPak video",
|
|
PublishedAt: now.Add(-time.Hour),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
},
|
|
}
|
|
for _, v := range videos {
|
|
if err := cat.UpsertVideo(ctx, v); err != nil {
|
|
t.Fatalf("seed video %s: %v", v.ID, err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/api/videos?driveId=OneDrive", nil)
|
|
rr := httptest.NewRecorder()
|
|
(&AdminServer{Catalog: cat}).handleAdminListVideos(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
var got struct {
|
|
Items []catalog.Video `json:"items"`
|
|
Total int `json:"total"`
|
|
}
|
|
if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.Total != 1 || len(got.Items) != 1 {
|
|
t.Fatalf("response total/items = %d/%d, want 1/1: %#v", got.Total, len(got.Items), got.Items)
|
|
}
|
|
if got.Items[0].DriveID != "OneDrive" || got.Items[0].ID != "od-video" {
|
|
t.Fatalf("item = %#v, want OneDrive od-video", got.Items[0])
|
|
}
|
|
}
|
|
|
|
func TestHandleAdminListVideosPaginates(t *testing.T) {
|
|
ctx := context.Background()
|
|
cat, err := catalog.Open(t.TempDir() + "/catalog.db")
|
|
if err != nil {
|
|
t.Fatalf("open catalog: %v", err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := cat.Close(); err != nil {
|
|
t.Fatalf("close catalog: %v", err)
|
|
}
|
|
})
|
|
|
|
now := time.Now()
|
|
for i, title := range []string{"first", "second", "third"} {
|
|
v := &catalog.Video{
|
|
ID: title,
|
|
DriveID: "OneDrive",
|
|
FileID: title + "-file",
|
|
Title: title,
|
|
PublishedAt: now.Add(-time.Duration(i) * time.Hour),
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := cat.UpsertVideo(ctx, v); err != nil {
|
|
t.Fatalf("seed video %s: %v", v.ID, err)
|
|
}
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/admin/api/videos?driveId=OneDrive&page=2&size=2", nil)
|
|
rr := httptest.NewRecorder()
|
|
(&AdminServer{Catalog: cat}).handleAdminListVideos(rr, req)
|
|
|
|
if rr.Code != http.StatusOK {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
var got struct {
|
|
Items []catalog.Video `json:"items"`
|
|
Total int `json:"total"`
|
|
Page int `json:"page"`
|
|
Size int `json:"size"`
|
|
}
|
|
if err := json.NewDecoder(rr.Body).Decode(&got); err != nil {
|
|
t.Fatalf("decode: %v", err)
|
|
}
|
|
if got.Total != 3 || got.Page != 2 || got.Size != 2 {
|
|
t.Fatalf("pagination meta = total:%d page:%d size:%d, want 3/2/2", got.Total, got.Page, got.Size)
|
|
}
|
|
if len(got.Items) != 1 || got.Items[0].ID != "third" {
|
|
t.Fatalf("items = %#v, want only third", got.Items)
|
|
}
|
|
}
|
|
|
|
func TestHandleRegenAllPreviewsInvokesHook(t *testing.T) {
|
|
called := false
|
|
server := &AdminServer{
|
|
OnRegenAllPreviews: func() {
|
|
called = true
|
|
},
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/api/videos/regen-preview", nil)
|
|
rr := httptest.NewRecorder()
|
|
server.handleRegenAllPreviews(rr, req)
|
|
|
|
if rr.Code != http.StatusAccepted {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
if !called {
|
|
t.Fatal("regen all previews hook was not called")
|
|
}
|
|
}
|
|
|
|
func TestHandleRegenFailedPreviewsInvokesHookWithDriveID(t *testing.T) {
|
|
calledWith := ""
|
|
server := &AdminServer{
|
|
OnRegenFailedPreviews: func(driveID string) {
|
|
calledWith = driveID
|
|
},
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/admin/api/drives/PikPak/previews/failed/regenerate", nil)
|
|
rctx := chi.NewRouteContext()
|
|
rctx.URLParams.Add("id", "PikPak")
|
|
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, rctx))
|
|
rr := httptest.NewRecorder()
|
|
server.handleRegenFailedPreviews(rr, req)
|
|
|
|
if rr.Code != http.StatusAccepted {
|
|
t.Fatalf("status = %d, body = %s", rr.Code, rr.Body.String())
|
|
}
|
|
if calledWith != "PikPak" {
|
|
t.Fatalf("hook called with %q, want PikPak", calledWith)
|
|
}
|
|
}
|