package handlers import ( "encoding/xml" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mk6i/open-oscar-server/server/webapi/types" "github.com/mk6i/open-oscar-server/state" ) // renderXML sends payload through the f=xml path and returns the body. func renderXML(t *testing.T, data any) string { t.Helper() resp := BaseResponse{} resp.Response.StatusCode = 200 resp.Response.StatusText = "Ok" resp.Response.RequestID = "123" resp.Response.Data = data rr := httptest.NewRecorder() SendResponse(rr, httptest.NewRequest(http.MethodGet, "/x?f=xml&r=123", nil), resp, nil) body := rr.Body.String() require.Contains(t, rr.Header().Get("Content-Type"), "xml") // A document the client could not parse is the failure this guards against. var probe any require.NoError(t, xml.Unmarshal([]byte(body), &probe), "not well-formed: %s", body) return body } // The spec renders the envelope as a flat root carrying statusCode, // statusText, requestId and data — not the "response"-keyed nesting JSON uses. func TestXMLEnvelopeMatchesSpec(t *testing.T) { body := renderXML(t, struct { AimSID string `xml:"aimsid"` }{AimSID: "opaquedata"}) assert.Contains(t, body, ``) assert.Contains(t, body, "200Ok"+ "123opaquedata") } // Every method in the spec renders a data element even when it carries no // payload; the client dereferences response.data regardless of outcome. func TestXMLAlwaysRendersData(t *testing.T) { rr := httptest.NewRecorder() resp := BaseResponse{} resp.Response.StatusCode = 200 resp.Response.StatusText = "Ok" SendResponse(rr, httptest.NewRequest(http.MethodGet, "/aim/endSession?f=xml", nil), resp, nil) assert.Contains(t, rr.Body.String(), "") } // The item names inside a list element are fixed by the spec and cannot be // derived from the container's name (allows holds allow, not user). func TestXMLItemNamesMatchSpec(t *testing.T) { t.Run("buddy list", func(t *testing.T) { body := renderXML(t, &BuddyListData{Groups: []WebAPIBuddyGroup{{ Name: "Friends", Buddies: []WebAPIBuddyInfo{{ AimID: "chattingchuck", DisplayID: "ChattingChuck", State: "away", UserType: "aim", Capabilities: []string{"200A0000A28911D3A52D001083341CFA"}, }}, }}}) assert.Contains(t, body, "Friends") assert.Contains(t, body, "chattingchuck") assert.Contains(t, body, "200A0000A28911D3A52D001083341CFA") // The Go field names must not leak through as element names. assert.NotContains(t, body, "") assert.NotContains(t, body, "") }) t.Run("permit deny", func(t *testing.T) { body := renderXML(t, PermitDenyData{ PDMode: "permitOnList", PermitList: []string{"ChattingChuck"}, DenyList: []string{"fred"}, }) assert.Contains(t, body, "permitOnList") assert.Contains(t, body, "ChattingChuck") assert.Contains(t, body, "fred") }) t.Run("presence", func(t *testing.T) { body := renderXML(t, PresenceData{Users: []BuddyPresenceInfo{{ AimID: "chattingchuck", State: "away", UserType: "aim", }}}) assert.Contains(t, body, "chattingchuck") }) t.Run("fetch events", func(t *testing.T) { body := renderXML(t, &FetchEventsData{ Events: []types.Event{{ Type: types.EventTypeTyping, SeqNum: 7, Timestamp: 100, Data: types.TypingEvent{AimID: "chattingchuck", TypingStatus: "typing"}, }}, LastSeqNum: 7, }) assert.Contains(t, body, "typing7") assert.Contains(t, body, "chattingchucktyping") }) } // myInfo is the payload the spec documents in the most detail, and the one the // old hand-built XML truncated to two fields. func TestXMLMyInfoCarriesEveryField(t *testing.T) { mi := buildMyInfo(state.DisplayScreenName("ChattingChuck"), "away", "http://host/icon") mi.OnlineTime = 100 mi.AwayMsg = "I'm busy right now chatting." body := renderXML(t, mi) for _, want := range []string{ "chattingchuck", "ChattingChuck", "ChattingChuck", "away", "aim", "I'm busy right now chatting.", "http://host/icon", "100", } { assert.Contains(t, body, want) } } // Preferences are the one payload that is legitimately partial, so an absent // preference and one set to zero have to stay distinguishable in XML too. func TestXMLPreferencesDistinguishAbsentFromZero(t *testing.T) { prefs := &PreferenceData{} prefs.Set("showGroups", 0) body := renderXML(t, prefs) assert.Contains(t, body, "0") assert.NotContains(t, body, "") }