Files
meta-core/test/metadata_test.go
2026-01-31 19:48:12 +01:00

525 lines
14 KiB
Go

package test
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
)
// =============================================================================
// Mock Types for Testing
// =============================================================================
// MockMetadataListItem matches the API response structure
type MockMetadataListItem struct {
HashID string `json:"hashId"`
Title string `json:"title,omitempty"`
FileName string `json:"fileName,omitempty"`
FilePath string `json:"filePath,omitempty"`
}
// MockMetadataListResponse matches GET /api/metadata/list response
type MockMetadataListResponse struct {
Items []MockMetadataListItem `json:"items"`
Count int `json:"count"`
Total int `json:"total"`
}
// MockHashIDsResponse matches GET /api/metadata/hash-ids response
type MockHashIDsResponse struct {
HashIds []string `json:"hashIds"`
Count int `json:"count"`
}
// MockMetadataResponse matches GET /api/metadata/{hashId} response
type MockMetadataResponse struct {
HashID string `json:"hashId"`
Metadata map[string]string `json:"metadata"`
}
// MockPropertyResponse matches GET /api/metadata/{hashId}/property response
type MockPropertyResponse struct {
HashID string `json:"hashId"`
Property string `json:"property"`
Value string `json:"value"`
}
// MockSearchResult matches search result structure
type MockSearchResult struct {
HashID string `json:"hashId"`
Metadata map[string]string `json:"metadata"`
}
// MockSearchResponse matches POST /api/metadata/search response
type MockSearchResponse struct {
Results []MockSearchResult `json:"results"`
Count int `json:"count"`
Total int `json:"total"`
}
// MockKVInfoResponse matches GET /api/kv/info response
type MockKVInfoResponse struct {
Prefix string `json:"prefix"`
FileCount int `json:"fileCount"`
KeyCount int `json:"keyCount"`
TotalSize int64 `json:"totalSize"`
MemoryUsage string `json:"memoryUsage"`
}
// MockKVKeysResponse matches GET /api/kv/keys response
type MockKVKeysResponse struct {
Keys []string `json:"keys"`
Cursor string `json:"cursor"`
HasMore bool `json:"hasMore"`
}
// MockKVKeyValueResponse matches GET /api/kv/key/{key} response
type MockKVKeyValueResponse struct {
Key string `json:"key"`
Type string `json:"type"`
Value map[string]string `json:"value"`
}
// =============================================================================
// Metadata API Response Format Tests
// =============================================================================
func TestMetadataListResponseFormat(t *testing.T) {
response := MockMetadataListResponse{
Items: []MockMetadataListItem{
{HashID: "hash001", FileName: "movie.mp4", FilePath: "/files/movie.mp4"},
{HashID: "hash002", FileName: "show.mkv", FilePath: "/files/show.mkv"},
},
Count: 2,
Total: 2,
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
// Check required fields
if _, exists := parsed["items"]; !exists {
t.Error("Missing required field: items")
}
if _, exists := parsed["count"]; !exists {
t.Error("Missing required field: count")
}
if _, exists := parsed["total"]; !exists {
t.Error("Missing required field: total")
}
}
func TestMetadataHashIdsResponseFormat(t *testing.T) {
response := MockHashIDsResponse{
HashIds: []string{"hash001", "hash002", "hash003"},
Count: 3,
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if _, exists := parsed["hashIds"]; !exists {
t.Error("Missing required field: hashIds")
}
if _, exists := parsed["count"]; !exists {
t.Error("Missing required field: count")
}
}
func TestMetadataByHashIdResponseFormat(t *testing.T) {
response := MockMetadataResponse{
HashID: "hash001",
Metadata: map[string]string{
"fileName": "Test Movie.mp4",
"videoType": "movie",
"title": "Test Movie",
},
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if _, exists := parsed["hashId"]; !exists {
t.Error("Missing required field: hashId")
}
if _, exists := parsed["metadata"]; !exists {
t.Error("Missing required field: metadata")
}
}
func TestMetadataPropertyResponseFormat(t *testing.T) {
response := MockPropertyResponse{
HashID: "hash001",
Property: "videoType",
Value: "movie",
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
requiredFields := []string{"hashId", "property", "value"}
for _, field := range requiredFields {
if _, exists := parsed[field]; !exists {
t.Errorf("Missing required field: %s", field)
}
}
}
func TestMetadataSearchResponseFormat(t *testing.T) {
response := MockSearchResponse{
Results: []MockSearchResult{
{
HashID: "hash001",
Metadata: map[string]string{"fileName": "movie.mp4"},
},
},
Count: 1,
Total: 10,
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
requiredFields := []string{"results", "count", "total"}
for _, field := range requiredFields {
if _, exists := parsed[field]; !exists {
t.Errorf("Missing required field: %s", field)
}
}
}
// =============================================================================
// KV Browser API Response Format Tests
// =============================================================================
func TestKVInfoResponseFormat(t *testing.T) {
response := MockKVInfoResponse{
Prefix: "file:",
FileCount: 100,
KeyCount: 100,
TotalSize: 1024000,
MemoryUsage: "1.5M",
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
requiredFields := []string{"prefix", "fileCount", "keyCount", "totalSize", "memoryUsage"}
for _, field := range requiredFields {
if _, exists := parsed[field]; !exists {
t.Errorf("Missing required field: %s", field)
}
}
}
func TestKVKeysResponseFormat(t *testing.T) {
response := MockKVKeysResponse{
Keys: []string{"file:hash001", "file:hash002"},
Cursor: "2",
HasMore: true,
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
requiredFields := []string{"keys", "cursor", "hasMore"}
for _, field := range requiredFields {
if _, exists := parsed[field]; !exists {
t.Errorf("Missing required field: %s", field)
}
}
}
func TestKVKeyValueResponseFormat(t *testing.T) {
response := MockKVKeyValueResponse{
Key: "file:hash001",
Type: "hash",
Value: map[string]string{
"fileName": "movie.mp4",
"title": "Test Movie",
},
}
data, err := json.Marshal(response)
if err != nil {
t.Fatalf("Failed to marshal: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
requiredFields := []string{"key", "type", "value"}
for _, field := range requiredFields {
if _, exists := parsed[field]; !exists {
t.Errorf("Missing required field: %s", field)
}
}
}
// =============================================================================
// Metadata API Route Registration Tests
// =============================================================================
func TestMetadataAPIRoutes(t *testing.T) {
router := mux.NewRouter()
// Register Metadata API routes (matching server.go)
metadataRoutes := []struct {
path string
method string
}{
{"/api/metadata/hash-ids", "GET"},
{"/api/metadata/list", "GET"},
{"/api/metadata/search", "POST"},
{"/api/metadata/batch", "POST"},
{"/api/metadata/clear", "POST"},
{"/api/metadata/{hashId}/property", "GET"},
{"/api/metadata/{hashId}/property", "PUT"},
{"/api/metadata/{hashId}", "GET"},
{"/api/metadata/{hashId}", "PUT"},
{"/api/metadata/{hashId}", "DELETE"},
}
// Register dummy handlers
for _, route := range metadataRoutes {
path := route.path
router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}).Methods(route.method)
}
// Test cases with actual path values
testCases := []struct {
method string
path string
}{
{"GET", "/api/metadata/hash-ids"},
{"GET", "/api/metadata/list"},
{"GET", "/api/metadata/list?limit=10&offset=0"},
{"POST", "/api/metadata/search"},
{"POST", "/api/metadata/batch"},
{"POST", "/api/metadata/clear"},
{"GET", "/api/metadata/testhash001/property?property=videoType"},
{"PUT", "/api/metadata/testhash001/property"},
{"GET", "/api/metadata/testhash001"},
{"PUT", "/api/metadata/testhash001"},
{"DELETE", "/api/metadata/testhash001"},
}
for _, tc := range testCases {
var body *bytes.Reader
if tc.method == "POST" || tc.method == "PUT" {
body = bytes.NewReader([]byte(`{}`))
}
var req *http.Request
if body != nil {
req = httptest.NewRequest(tc.method, tc.path, body)
req.Header.Set("Content-Type", "application/json")
} else {
req = httptest.NewRequest(tc.method, tc.path, nil)
}
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("Metadata API route %s %s returned status %d, expected 200", tc.method, tc.path, rr.Code)
}
}
}
// =============================================================================
// KV Browser API Route Registration Tests
// =============================================================================
func TestKVBrowserAPIRoutes(t *testing.T) {
router := mux.NewRouter()
// Register KV Browser API routes (matching server.go)
kvRoutes := []struct {
path string
method string
}{
{"/api/kv/info", "GET"},
{"/api/kv/keys", "GET"},
{"/api/kv/key/{key:.*}", "GET"},
}
// Register dummy handlers
for _, route := range kvRoutes {
path := route.path
router.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}).Methods(route.method)
}
// Test cases
testCases := []struct {
method string
path string
}{
{"GET", "/api/kv/info"},
{"GET", "/api/kv/keys"},
{"GET", "/api/kv/keys?cursor=0&count=50"},
{"GET", "/api/kv/key/file:testhash001"},
}
for _, tc := range testCases {
req := httptest.NewRequest(tc.method, tc.path, nil)
rr := httptest.NewRecorder()
router.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Errorf("KV Browser API route %s %s returned status %d, expected 200", tc.method, tc.path, rr.Code)
}
}
}
// =============================================================================
// Error Response Tests
// =============================================================================
func TestMetadataAPIErrorResponses(t *testing.T) {
// Test that error responses have the expected format
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message,omitempty"`
}
errResp := ErrorResponse{
Error: "metadata not found",
Message: "No metadata found for the given hash ID",
}
data, err := json.Marshal(errResp)
if err != nil {
t.Fatalf("Failed to marshal error response: %v", err)
}
var parsed map[string]interface{}
if err := json.Unmarshal(data, &parsed); err != nil {
t.Fatalf("Failed to unmarshal: %v", err)
}
if _, exists := parsed["error"]; !exists {
t.Error("Error response missing 'error' field")
}
}
// =============================================================================
// Pagination Tests
// =============================================================================
func TestMetadataListPagination(t *testing.T) {
// Test pagination parameter handling
testCases := []struct {
name string
offset int
limit int
}{
{"default pagination", 0, 100},
{"custom offset", 10, 100},
{"custom limit", 0, 50},
{"both custom", 20, 25},
}
for _, tc := range testCases {
// Simulate response with pagination
response := MockMetadataListResponse{
Items: make([]MockMetadataListItem, tc.limit),
Count: tc.limit,
Total: 1000,
}
if response.Count != tc.limit {
t.Errorf("%s: expected count %d, got %d", tc.name, tc.limit, response.Count)
}
}
}
func TestKVKeysPagination(t *testing.T) {
// Test cursor-based pagination
testCases := []struct {
name string
cursor int
count int
total int
hasMore bool
}{
{"first page", 0, 50, 100, true},
{"second page", 50, 50, 100, false},
{"small result", 0, 50, 10, false},
}
for _, tc := range testCases {
response := MockKVKeysResponse{
Keys: make([]string, tc.count),
Cursor: "50",
HasMore: tc.hasMore,
}
if response.HasMore != tc.hasMore {
t.Errorf("%s: expected hasMore %v, got %v", tc.name, tc.hasMore, response.HasMore)
}
}
}