package models import ( "encoding/xml" "testing" ) func TestServiceRecent_Parity(t *testing.T) { t.Run("Unmarshal local response (nested contentItem)", func(t *testing.T) { localXML := ` Coco, Pt. 1 2026-03-14T22:39:17.000+00:00 2026-03-14T22:39:17.000+00:00 2026-03-22T10:53:48.000+00:00 10863533 ` var recent ServiceRecent err := xml.Unmarshal([]byte(localXML), &recent) if err != nil { t.Fatalf("Unmarshal failed: %v", err) } if recent.ID != "2568595253" { t.Errorf("Expected ID 2568595253, got %s", recent.ID) } if recent.Name != "Coco, Pt. 1" { t.Errorf("Expected Name 'Coco, Pt. 1', got %s", recent.Name) } if recent.SourceID != "10863533" { t.Errorf("Expected SourceID 10863533, got %s", recent.SourceID) } }) t.Run("Unmarshal upstream response (flat contentItem)", func(t *testing.T) { upstreamXML := ` tracklisturl 2026-03-22T10:00:04.000+00:00 2026-03-22T10:53:48.000+00:00 /playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP Dopamine 2016-01-06T08:52:04.000+00:00 TOKEN user-name 15 user-name@mail.internal 2020-04-25T20:29:11.000+00:00 user-name 10863533 2026-03-22T10:53:50.719+00:00 ` var recent ServiceRecent err := xml.Unmarshal([]byte(upstreamXML), &recent) if err != nil { t.Fatalf("Unmarshal failed: %v", err) } if recent.ID != "2569047180" { t.Errorf("Expected ID 2569047180, got %s", recent.ID) } if recent.Name != "Dopamine" { t.Errorf("Expected Name 'Dopamine', got %s", recent.Name) } if recent.ContentItemType != "tracklisturl" { t.Errorf("Expected ContentItemType 'tracklisturl', got %s", recent.ContentItemType) } if recent.Location != "/playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP" { t.Errorf("Expected Location '/playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP', got %s", recent.Location) } if recent.SourceID != "10863533" { t.Errorf("Expected SourceID 10863533, got %s", recent.SourceID) } }) t.Run("Marshal ServiceRecent should follow local style (nested)", func(t *testing.T) { recent := ServiceRecent{ ServiceContentItem: ServiceContentItem{ ID: "2569047180", Name: "Dopamine", ContentItemType: "tracklisturl", Location: "/playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP", SourceID: "10863533", Source: "SPOTIFY", Type: "tracklisturl", SourceAccount: "user-name", IsPresetable: "true", }, CreatedOn: "2026-03-22T10:00:04.000+00:00", UpdatedOn: "2026-03-22T10:53:50.719+00:00", LastPlayedAt: "2026-03-22T10:53:48.000+00:00", } data, err := xml.MarshalIndent(recent, "", " ") if err != nil { t.Fatalf("Marshal failed: %v", err) } xmlStr := string(data) if !contains_substr(xmlStr, "Dopamine") { t.Errorf("Marshaled ServiceRecent missing nested element\nGot: %s", xmlStr) } }) t.Run("Marshal RecentItemParity should follow upstream style (flat)", func(t *testing.T) { recent := RecentItemParity{ ID: "2569047180", Name: "Dopamine", ContentItemType: "tracklisturl", Location: "/playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP", SourceID: "10863533", CreatedOn: "2026-03-22T10:00:04.000+00:00", UpdatedOn: "2026-03-22T10:53:50.719+00:00", LastPlayedAt: "2026-03-22T10:53:48.000+00:00", Source: &RecentItemParitySource{ ID: "10863533", Type: "Audio", Credential: &RecentItemParityCredential{ Type: "token", Value: "", }, }, } data, err := xml.MarshalIndent(recent, "", " ") if err != nil { t.Fatalf("Marshal failed: %v", err) } xmlStr := string(data) expectedElements := []string{ ``, `tracklisturl`, `2026-03-22T10:00:04.000+00:00`, `2026-03-22T10:53:48.000+00:00`, `/playback/container/c3BvdGlmeTphbGJ1bTowMUpRS3RjQ1hIZGppVHpHRFk3NXhP`, `Dopamine`, `10863533`, `2026-03-22T10:53:50.719+00:00`, ``, } for _, expected := range expectedElements { if !contains_substr(xmlStr, expected) { t.Errorf("Marshaled XML missing expected element: %s\nGot: %s", expected, xmlStr) } } // It should NOT have nested contentItem if contains_substr(xmlStr, "") { t.Errorf("Marshaled RecentItemParity should not have nested element\nGot: %s", xmlStr) } }) t.Run("Round-trip: Nested XML -> ServiceRecent -> Unmarshal -> Marshal -> Nested XML", func(t *testing.T) { nestedXML := ` Coco, Pt. 1 ` var recent1 ServiceRecent if err := xml.Unmarshal([]byte(nestedXML), &recent1); err != nil { t.Fatalf("Unmarshal nested failed: %v", err) } // Marshal it (should produce nested XML again) nestedData, err := xml.MarshalIndent(recent1, "", " ") if err != nil { t.Fatalf("Marshal failed: %v", err) } xmlStr := string(nestedData) if !contains_substr(xmlStr, "Coco, Pt. 1") { t.Errorf("Round-trip failed to maintain nested structure\nGot: %s", xmlStr) } }) } func contains_substr(s, substr string) bool { return len(s) >= len(substr) && (s == substr || (len(substr) > 0 && (s[:len(substr)] == substr || contains_substr(s[1:], substr)))) }