525 lines
14 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|