//go:build test package photos import ( "encoding/json" "testing" ) func TestParseAlbumsJSON(t *testing.T) { tests := []struct { name string json string want []Album wantErr bool }{ { name: "empty albums", json: `{"albums":[]}`, want: []Album{}, }, { name: "single album", json: `{"albums":[{"id":"abc","title":"Vacation"}]}`, want: []Album{{ID: "abc", Title: "Vacation"}}, }, { name: "multiple albums", json: `{"albums":[{"id":"a1","title":"Album1"},{"id":"a2","title":"Album2"}]}`, want: []Album{{ID: "a1", Title: "Album1"}, {ID: "a2", Title: "Album2"}}, }, { name: "invalid json", json: `not json`, wantErr: true, }, { name: "missing albums key", json: `{}`, want: []Album{}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseAlbumsJSON(tt.json) if (err != nil) != tt.wantErr { t.Errorf("ParseAlbumsJSON() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { return } if len(got) != len(tt.want) { t.Errorf("ParseAlbumsJSON() got %d albums, want %d", len(got), len(tt.want)) return } for i := range got { if got[i] != tt.want[i] { t.Errorf("ParseAlbumsJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i]) } } }) } } func TestParseAssetsJSON(t *testing.T) { tests := []struct { name string json string want []Asset wantTotal int wantErr bool errMsg string }{ { name: "empty assets", json: `{"assets":[],"total":0}`, want: []Asset{}, wantTotal: 0, }, { name: "single asset", json: `{"assets":[{"id":"asset1","filename":"IMG_0001.JPG"}],"total":1}`, want: []Asset{{ID: "asset1", Filename: "IMG_0001.JPG"}}, wantTotal: 1, }, { name: "multiple assets", json: `{"assets":[{"id":"a1","filename":"a.jpg"},{"id":"a2","filename":"b.jpg"},{"id":"a3","filename":"c.jpg"}],"total":3}`, want: []Asset{{ID: "a1", Filename: "a.jpg"}, {ID: "a2", Filename: "b.jpg"}, {ID: "a3", Filename: "c.jpg"}}, wantTotal: 3, }, { name: "error response", json: `{"error":"album not found"}`, wantErr: true, errMsg: "album not found", }, { name: "invalid json", json: `not json`, wantErr: true, }, { name: "empty error is not an error", json: `{"error":"","assets":[{"id":"a1","filename":"IMG.JPG"}],"total":1}`, want: []Asset{{ID: "a1", Filename: "IMG.JPG"}}, wantTotal: 1, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, total, err := ParseAssetsJSON(tt.json) if (err != nil) != tt.wantErr { t.Errorf("ParseAssetsJSON() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if tt.errMsg != "" && err.Error() != tt.errMsg { t.Errorf("ParseAssetsJSON() error = %q, want %q", err.Error(), tt.errMsg) } return } if total != tt.wantTotal { t.Errorf("ParseAssetsJSON() total = %d, want %d", total, tt.wantTotal) } if len(got) != len(tt.want) { t.Errorf("ParseAssetsJSON() got %d assets, want %d", len(got), len(tt.want)) return } for i := range got { if got[i] != tt.want[i] { t.Errorf("ParseAssetsJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i]) } } }) } } func TestParseTreeJSON(t *testing.T) { tests := []struct { name string json string want []CollectionNode wantErr bool errMsg string }{ { name: "empty tree", json: `{"collections":[]}`, want: []CollectionNode{}, }, { name: "nested tree", json: `{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"f2","name":"Italy 2024","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album","children":[]}]}]},{"id":"a2","name":"Favorites","kind":"album","children":[]}]}`, want: []CollectionNode{ {ID: "f1", Name: "Trips", Kind: "folder", Children: []CollectionNode{{ID: "f2", Name: "Italy 2024", Kind: "folder", Children: []CollectionNode{{ID: "a1", Name: "Venice", Kind: "album"}}}}}, {ID: "a2", Name: "Favorites", Kind: "album"}, }, }, { name: "error response", json: `{"error":"library unavailable"}`, wantErr: true, errMsg: "library unavailable", }, { name: "invalid json", json: `not json`, wantErr: true, }, { name: "missing collections key", json: `{}`, want: nil, }, { name: "empty error is not an error", json: `{"error":"","collections":[{"id":"a9","name":"Root Album","kind":"album"}]}`, want: []CollectionNode{{ID: "a9", Name: "Root Album", Kind: "album"}}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseTreeJSON(tt.json) if (err != nil) != tt.wantErr { t.Errorf("ParseTreeJSON() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if tt.errMsg != "" && err.Error() != tt.errMsg { t.Errorf("ParseTreeJSON() error = %q, want %q", err.Error(), tt.errMsg) } return } if len(got) != len(tt.want) { t.Errorf("ParseTreeJSON() got %d collections, want %d", len(got), len(tt.want)) return } for i := range got { if !equalCollectionNode(got[i], tt.want[i]) { t.Errorf("ParseTreeJSON()[%d] = %+v, want %+v", i, got[i], tt.want[i]) } } }) } } func TestAlbumsResponseUnmarshal(t *testing.T) { raw := `{"albums":[{"id":"x","title":"Y"}]}` var resp AlbumsResponse if err := json.Unmarshal([]byte(raw), &resp); err != nil { t.Fatal(err) } if len(resp.Albums) != 1 || resp.Albums[0].ID != "x" || resp.Albums[0].Title != "Y" { t.Errorf("got %+v", resp.Albums) } } func TestAssetsResponseUnmarshal(t *testing.T) { raw := `{"assets":[{"id":"z","filename":"IMG_9999.JPG"}],"total":1}` var resp AssetsResponse if err := json.Unmarshal([]byte(raw), &resp); err != nil { t.Fatal(err) } if len(resp.Assets) != 1 || resp.Assets[0].ID != "z" || resp.Assets[0].Filename != "IMG_9999.JPG" { t.Errorf("got %+v", resp.Assets) } if resp.Total != 1 { t.Errorf("got total %d, want 1", resp.Total) } } func TestTreeResponseUnmarshal(t *testing.T) { raw := `{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]}` var resp TreeResponse if err := json.Unmarshal([]byte(raw), &resp); err != nil { t.Fatal(err) } if len(resp.Collections) != 1 || resp.Collections[0].ID != "f1" || resp.Collections[0].Name != "Trips" || len(resp.Collections[0].Children) != 1 || resp.Collections[0].Children[0].ID != "a1" || resp.Collections[0].Children[0].Name != "Venice" { t.Errorf("got %+v", resp.Collections) } } func TestErrorResponseUnmarshal(t *testing.T) { raw := `{"error":"oops"}` var resp ErrorResponse if err := json.Unmarshal([]byte(raw), &resp); err != nil { t.Fatal(err) } if resp.Error != "oops" { t.Errorf("got %q", resp.Error) } } func TestCgoBridgeImplementsBridge(t *testing.T) { var _ Bridge = &CgoBridge{} } func TestCgoBridgeListAlbumsViaStub(t *testing.T) { SetTestAlbumsJSON(`{"albums":[{"id":"abc","title":"TestAlbum"}]}`) bridge := &CgoBridge{} albums, err := bridge.ListAlbums() if err != nil { t.Fatal(err) } if len(albums) != 1 || albums[0].ID != "abc" || albums[0].Title != "TestAlbum" { t.Errorf("got %+v", albums) } } func TestCgoBridgeListAssetsViaStub(t *testing.T) { SetTestAssetsJSON(`{"assets":[{"id":"asset-1","filename":"A.JPG"},{"id":"asset-2","filename":"B.JPG"}],"total":2}`) bridge := &CgoBridge{} assets, _, err := bridge.ListAssets("any") if err != nil { t.Fatal(err) } if len(assets) != 2 { t.Errorf("got %d assets", len(assets)) } if assets[0].Filename != "A.JPG" || assets[1].Filename != "B.JPG" { t.Errorf("got %+v", assets) } } func TestCgoBridgeListAssetsErrorViaStub(t *testing.T) { SetTestAssetsJSON(`{"error":"album not found"}`) bridge := &CgoBridge{} _, _, err := bridge.ListAssets("bad-id") if err == nil || err.Error() != "album not found" { t.Errorf("got %v", err) } } func TestCgoBridgeListTreeViaStub(t *testing.T) { SetTestTreeJSON(`{"collections":[{"id":"f1","name":"Trips","kind":"folder","children":[{"id":"f2","name":"Italy 2024","kind":"folder","children":[{"id":"a1","name":"Venice","kind":"album"}]}]},{"id":"a2","name":"Favorites","kind":"album"}]}`) bridge := &CgoBridge{} tree, err := bridge.ListTree() if err != nil { t.Fatal(err) } if len(tree) != 2 { t.Errorf("got %d collections", len(tree)) } if tree[0].ID != "f1" || tree[0].Name != "Trips" || len(tree[0].Children) != 1 || tree[0].Children[0].ID != "f2" || tree[0].Children[0].Name != "Italy 2024" { t.Errorf("got %+v", tree) } if tree[1].ID != "a2" || tree[1].Name != "Favorites" || tree[1].Kind != "album" { t.Errorf("got %+v", tree) } } func TestCgoBridgeListTreeNil(t *testing.T) { SetTestTreeNull() defer SetTestTreeJSON(`{"collections":[]}`) bridge := &CgoBridge{} _, err := bridge.ListTree() if err != errBridgeNil { t.Errorf("got %v, want %v", err, errBridgeNil) } } func TestCgoBridgeRequestAccessGranted(t *testing.T) { SetTestAccessRC(0) bridge := &CgoBridge{} if err := bridge.RequestAccess(); err != nil { t.Errorf("got %v", err) } } func TestCgoBridgeRequestAccessDenied(t *testing.T) { SetTestAccessRC(-1) bridge := &CgoBridge{} err := bridge.RequestAccess() if err == nil { t.Fatal("expected error") } if err != errAccessDenied { t.Errorf("got %v, want %v", err, errAccessDenied) } } func TestCgoBridgeListAlbumsNil(t *testing.T) { SetTestAlbumsNull() defer SetTestAlbumsJSON(`{"albums":[]}`) bridge := &CgoBridge{} _, err := bridge.ListAlbums() if err != errBridgeNil { t.Errorf("got %v, want %v", err, errBridgeNil) } } func TestCgoBridgeListAssetsNil(t *testing.T) { SetTestAssetsNull() defer SetTestAssetsJSON(`{"assets":[],"total":0}`) bridge := &CgoBridge{} _, _, err := bridge.ListAssets("any") if err != errBridgeNil { t.Errorf("got %v, want %v", err, errBridgeNil) } } func TestErrAccessDeniedMessage(t *testing.T) { if errAccessDenied.Error() == "" { t.Error("errAccessDenied should have a message") } } func TestErrBridgeNilMessage(t *testing.T) { if errBridgeNil.Error() == "" { t.Error("errBridgeNil should have a message") } } func TestParseExportResultJSON(t *testing.T) { tests := []struct { name string json string want ExportResult wantErr bool errMsg string }{ { name: "success", json: `{"filename":"0000_test.jpg","size":1024,"cloud":"local"}`, want: ExportResult{Filename: "0000_test.jpg", Size: 1024, Cloud: "local"}, }, { name: "cloud asset", json: `{"filename":"photo.jpg","size":2048,"cloud":"cloud"}`, want: ExportResult{Filename: "photo.jpg", Size: 2048, Cloud: "cloud"}, }, { name: "error response", json: `{"error":"write failed","cloud":"local"}`, wantErr: true, errMsg: "write failed", }, { name: "invalid json", json: `not json`, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := ParseExportResultJSON(tt.json) if (err != nil) != tt.wantErr { t.Errorf("ParseExportResultJSON() error = %v, wantErr %v", err, tt.wantErr) return } if tt.wantErr { if tt.errMsg != "" && err.Error() != tt.errMsg { t.Errorf("ParseExportResultJSON() error = %q, want %q", err.Error(), tt.errMsg) } return } if got != tt.want { t.Errorf("ParseExportResultJSON() = %+v, want %+v", got, tt.want) } }) } } func equalCollectionNode(a, b CollectionNode) bool { if a.ID != b.ID || a.Name != b.Name || a.Kind != b.Kind || len(a.Children) != len(b.Children) { return false } for i := range a.Children { if !equalCollectionNode(a.Children[i], b.Children[i]) { return false } } return true }