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) } }