ultisuite-backend/internal/photos/client_test.go
R3D347HR4Y f0f0b31043 Implement Photos API robustness and quota integration
Improve Immich-backed photos endpoints with robust mapping/error handling, full albums CRUD, reliable list pagination/sorting/filtering, and shared Nextcloud quota checks before upload.
2026-05-22 21:09:13 +02:00

201 lines
5.6 KiB
Go

package photos
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestGetAssetsSupportsWrappedItemsResponse(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/assets" {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{
"items": []map[string]any{
{
"id": "a1",
"originalFileName": "a.jpg",
"fileCreatedAt": "2026-01-01T00:00:00Z",
"exifInfo": map[string]any{
"fileSizeInByte": int64(42),
},
},
},
})
}))
defer server.Close()
client := NewClient(server.URL)
assets, err := client.GetAssets(context.Background(), "key", 1, 50)
if err != nil {
t.Fatalf("GetAssets: %v", err)
}
if len(assets) != 1 || assets[0].FileSize != 42 {
t.Fatalf("assets = %+v", assets)
}
}
func TestGetAllAssetsPaginatesUpstream(t *testing.T) {
call := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
call++
page := r.URL.Query().Get("page")
if page == "1" {
batch := make([]Asset, assetsPageSize)
for i := range batch {
batch[i] = Asset{ID: fmt.Sprintf("full-%d", i)}
}
_ = json.NewEncoder(w).Encode(batch)
return
}
if page == "2" {
_ = json.NewEncoder(w).Encode([]Asset{
{ID: "tail-1"},
{ID: "tail-2"},
})
return
}
_ = json.NewEncoder(w).Encode([]Asset{})
}))
defer server.Close()
client := NewClient(server.URL)
assets, err := client.GetAllAssets(context.Background(), "key")
if err != nil {
t.Fatalf("GetAllAssets: %v", err)
}
if len(assets) != assetsPageSize+2 {
t.Fatalf("len = %d, want %d", len(assets), assetsPageSize+2)
}
if call != 2 {
t.Fatalf("upstream calls = %d, want 2", call)
}
}
func TestCreateAlbumMapsImmichPayload(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/albums" {
http.NotFound(w, r)
return
}
if r.Header.Get("x-api-key") != "key" {
t.Fatalf("api key = %q", r.Header.Get("x-api-key"))
}
var payload createAlbumPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("decode payload: %v", err)
}
if payload.AlbumName != "Trip" || payload.Description != "Summer" || len(payload.AssetIDs) != 1 || payload.AssetIDs[0] != "asset-1" {
t.Fatalf("payload = %+v", payload)
}
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(Album{ID: "album-1", Name: payload.AlbumName})
}))
defer server.Close()
client := NewClient(server.URL)
album, err := client.CreateAlbum(context.Background(), "key", "Trip", "Summer", []string{"asset-1"})
if err != nil {
t.Fatalf("CreateAlbum: %v", err)
}
if album.ID != "album-1" || album.Name != "Trip" {
t.Fatalf("album = %+v", album)
}
}
func TestUploadAssetStatusMapped(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
defer server.Close()
client := NewClient(server.URL)
_, err := client.UploadAsset(context.Background(), "key", strings.NewReader("body"), "application/octet-stream")
if err == nil {
t.Fatal("expected error")
}
var statusErr *HTTPStatusError
if !errors.As(err, &statusErr) || statusErr.StatusCode != http.StatusBadRequest {
t.Fatalf("err = %v", err)
}
}
func TestGetAlbumNotFound(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}))
defer server.Close()
client := NewClient(server.URL)
_, err := client.GetAlbum(context.Background(), "key", "missing")
if err == nil {
t.Fatal("expected error")
}
var statusErr *HTTPStatusError
if !errors.As(err, &statusErr) || statusErr.StatusCode != http.StatusNotFound {
t.Fatalf("err = %v", err)
}
}
func TestAddAlbumAssetsSendsBulkIDs(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut || r.URL.Path != "/albums/album-1/assets" {
http.NotFound(w, r)
return
}
var payload bulkIDsPayload
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("decode payload: %v", err)
}
if len(payload.IDs) != 2 || payload.IDs[0] != "a1" || payload.IDs[1] != "a2" {
t.Fatalf("payload = %+v", payload)
}
_ = json.NewEncoder(w).Encode([]BulkAssetResult{{ID: "a1", Success: true}, {ID: "a2", Success: true}})
}))
defer server.Close()
client := NewClient(server.URL)
results, err := client.AddAlbumAssets(context.Background(), "key", "album-1", []string{"a1", "a2"})
if err != nil {
t.Fatalf("AddAlbumAssets: %v", err)
}
if len(results) != 2 {
t.Fatalf("results = %+v", results)
}
}
func TestRemoveAlbumAssetsUsesDeleteWithBody(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete || r.URL.Path != "/albums/album-1/assets" {
http.NotFound(w, r)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
t.Fatalf("read body: %v", err)
}
if string(body) != `{"ids":["a1"]}` {
t.Fatalf("body = %s", string(body))
}
_ = json.NewEncoder(w).Encode([]BulkAssetResult{{ID: "a1", Success: true}})
}))
defer server.Close()
client := NewClient(server.URL)
results, err := client.RemoveAlbumAssets(context.Background(), "key", "album-1", []string{"a1"})
if err != nil {
t.Fatalf("RemoveAlbumAssets: %v", err)
}
if len(results) != 1 || !results[0].Success {
t.Fatalf("results = %+v", results)
}
}