func TestSearchCompactParseCompact(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(compactDecoded)) cr, err := NewCompactSearchResult(body) testutils.Ok(t, err) testutils.Assert(t, StatusOK == cr.Response.Code, "bad code") testutils.Assert(t, "V2.7.0 2315: Success" == cr.Response.Text, "bad text") testutils.Assert(t, 10 == int(cr.Count), "bad count") testutils.Assert(t, 6 == len(cr.Columns), "bad header count") testutils.Assert(t, "A,B,C,D,E,F" == strings.Join(cr.Columns, ","), "bad headers") counter := 0 maxRows, err := cr.ForEach(func(row Row, err error) error { if strings.Join(row, ",") != "1,2,3,4,,6" { t.Errorf("bad row %d: %s", counter, row) } if err != nil { t.Errorf("error parsing body at row %d: %s", counter, err.Error()) } counter = counter + 1 return nil }) testutils.Ok(t, err) testutils.Assert(t, 8 == counter, "bad count") testutils.Assert(t, maxRows, "bad max rows") }
func TestSearchXMLComplex(t *testing.T) { type Listing struct { Business string `xml:"Business>RESIOWNS"` Approved bool `xml:"Listing>Approved"` MLS string `xml:"Listing>MLS"` Disclaimer string `xml:"Listing>Disclaimer"` Status string `xml:"Listing>Status"` ListingPrice float64 `xml:"Listing>Price>ListingPrice"` OriginalPrice float64 `xml:"Listing>Price>OriginalPrice"` SellPrice float64 `xml:"Listing>Price>SellingPrice"` } body := ioutil.NopCloser(strings.NewReader(standardXML)) cr, err := NewStandardXMLSearchResult(body) testutils.Ok(t, err) var listings []Listing count, maxRows, err := cr.ForEach(minidom.ByName("PropertyListing"), func(elem io.ReadCloser, err error) error { if err != nil { return err } listing := Listing{} xml.NewDecoder(elem).Decode(&listing) listings = append(listings, listing) return err }) testutils.Ok(t, err) testutils.Equals(t, true, maxRows) testutils.Equals(t, 10, count) testutils.Equals(t, 2, len(listings)) }
func TestConvertCompactMetadata(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(retsStart + system + resource + class + table + lookup + lookupType + retsEnd)) compact, err := rets.ParseMetadataCompactResult(body) testutils.Ok(t, err) msystem, err := AsStandard(*compact).Convert() testutils.Ok(t, err) testutils.Equals(t, "MLS System", msystem.System.Description) testutils.Equals(t, "1.12.30", string(msystem.Version)) testutils.Equals(t, "The System is provided to you by Systems.", msystem.System.Comments) mresource := msystem.System.MResource testutils.Equals(t, "1.12.30", string(mresource.Version)) testutils.Equals(t, 2, len(mresource.Resource)) mlookup := mresource.Resource[1].MLookup testutils.Equals(t, "1.12.29", string(mlookup.Version)) testutils.Equals(t, 4, len(mlookup.Lookup)) mlookupType := mlookup.Lookup[0].MLookupType testutils.Equals(t, "1.12.29", string(mlookupType.Version)) testutils.Equals(t, 4, len(mlookupType.LookupType)) agent := mresource.Resource[1] testutils.Equals(t, 2, len(agent.MClass.Class)) testutils.Equals(t, "Agent", string(agent.ResourceID)) testutils.Equals(t, "1.12.29", string(agent.MClass.Version)) }
func TestParseCompactData(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(compact)) decoder := DefaultXMLDecoder(body, false) token, err := decoder.Token() testutils.Ok(t, err) start, ok := token.(xml.StartElement) testutils.Assert(t, ok, "should be a start element") testutils.Equals(t, "METADATA-ELEMENT", start.Name.Local) cm, err := NewCompactData(start, decoder, " ") testutils.Ok(t, err) testutils.Equals(t, "METADATA-ELEMENT", cm.Element) testutils.Equals(t, "Dog", cm.Attr["Cat"]) testutils.Equals(t, 2, len(cm.CompactRows)) testutils.Equals(t, 2, len(cm.Columns())) }
func TestSimple(t *testing.T) { doms := ioutil.NopCloser(strings.NewReader(example)) parser := xml.NewDecoder(doms) listings := Listings{} // minidom isnt necessary but its crazy useful for massive streams md := minidom.MiniDom{ StartFunc: func(start xml.StartElement) { switch start.Name.Local { case "Listings": attrs := map[string]string{} for _, v := range start.Attr { attrs[v.Name.Local] = v.Value } listings.ListingsKey = attrs["listingsKey"] listings.Version = attrs["version"] listings.VersionTimestamp = attrs["versionTimestamp"] listings.Language = attrs["lang"] case "Disclaimer": parser.DecodeElement(listings.Disclaimer, &start) } }, // quit on the the xml tag EndFunc: minidom.QuitAt("Listings"), } err := md.Walk(parser, minidom.ByName("Listing"), ToListing(func(l Listing, err error) error { listings.Listings = append(listings.Listings, l) return err })) testutils.Ok(t, err) testutils.Equals(t, 1, len(listings.Listings)) testutils.Equals(t, "http://www.somemls.com/lisings/1234567890", listings.Listings[0].ListingURL) testutils.Equals(t, "New Light Fixtures", *listings.Listings[0].Photos[1].Caption) testutils.Equals(t, "1100.0", listings.Listings[0].Expenses[2].Value.Value) }
func TestParseClass(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(retsStart + class + retsEnd)) ms, err := ParseMetadataCompactResult(body) testutils.Ok(t, err) verifyParseClass(t, *ms) }
func TestSearchXML(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(standardXML)) cr, err := NewStandardXMLSearchResult(body) testutils.Ok(t, err) var listings []io.ReadCloser count, maxRows, err := cr.ForEach(minidom.ByName("PropertyListing"), func(elem io.ReadCloser, err error) error { listings = append(listings, elem) return err }) testutils.Ok(t, err) testutils.Equals(t, true, maxRows) testutils.Equals(t, 10, count) testutils.Equals(t, 2, len(listings)) }
func TestSearchCompactNoEof(t *testing.T) { rets := `<RETS ReplyCode="20201" ReplyText="No Records Found." ></RETS>` body := ioutil.NopCloser(strings.NewReader(rets)) cr, err := NewCompactSearchResult(body) testutils.Ok(t, err) testutils.Equals(t, StatusNoRecords, cr.Response.Code) }
func TestSystem(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(raw)) defer body.Close() extractor := &Extractor{Body: body} response, err := extractor.Open() testutils.Ok(t, err) testutils.Equals(t, "Operation Successful", response.ReplyText) xml := MSystem{} err = extractor.DecodeNext("METADATA-SYSTEM", &xml) if err != io.EOF { testutils.Ok(t, err) } testutils.Equals(t, "ABBA", xml.System.ID) testutils.Equals(t, "Property", string(xml.System.MResource.Resource[0].ResourceID)) }
func TestCompactEntry(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(compact)) decoder := DefaultXMLDecoder(body, false) token, err := decoder.Token() testutils.Ok(t, err) start, ok := token.(xml.StartElement) testutils.Assert(t, ok, "should be a start element") cm, err := NewCompactData(start, decoder, " ") testutils.Ok(t, err) type Test struct { ResourceID, Standardname string } row1 := Test{} maps := cm.Entries() maps[0].SetFields(&row1) testutils.Equals(t, "ActiveAgent", row1.ResourceID) testutils.Equals(t, "ActiveAgent", row1.Standardname) }
func TestSearchCompactEmbeddedRetsStatus(t *testing.T) { rets := `<?xml version="1.0" encoding="UTF-8" ?> <RETS ReplyCode="0" ReplyText="Operation Successful"> <RETS-STATUS ReplyCode="20201" ReplyText="No matching records were found" /> </RETS>` body := ioutil.NopCloser(strings.NewReader(rets)) cr, err := NewCompactSearchResult(body) testutils.Ok(t, err) testutils.Equals(t, StatusNoRecords, cr.Response.Code) }
func TestParseMetadata(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(retsStart + system + resource + class + table + lookup + lookupType + retsEnd)) ms, err := ParseMetadataCompactResult(body) testutils.Ok(t, err) verifySystem(t, *ms) verifyParseResources(t, *ms) verifyParseClass(t, *ms) verifyParseTable(t, *ms) verifyParseLookup(t, *ms) verifyParseLookupType(t, *ms) }
func TestNext(t *testing.T) { var raw = `<?xml version="1.0" encoding="utf-8"?> <RETS ReplyCode="0" ReplyText="Operation successful."> <METADATA> <METADATA-CLASS Version="01.72.11582" Date="2016-03-29T21:50:11" Resource="Agent"> </METADATA-CLASS> <METADATA-CLASS Version="01.72.11583" Date="2016-03-29T21:50:11" Resource="Office"> </METADATA-CLASS> <METADATA-CLASS Version="01.72.11584" Date="2016-03-29T21:50:11" Resource="Listing"> </METADATA-CLASS> </METADATA> </RETS>` body := ioutil.NopCloser(strings.NewReader(raw)) defer body.Close() extractor := &Extractor{Body: body} response, err := extractor.Open() testutils.Ok(t, err) testutils.Equals(t, "Operation successful.", response.ReplyText) next := func(resource, version, date string) func(*testing.T) { return func(tt *testing.T) { mclass := &MClass{} err = extractor.DecodeNext("METADATA-CLASS", mclass) testutils.Ok(t, err) testutils.Equals(tt, resource, string(mclass.Resource)) testutils.Equals(tt, version, string(mclass.Version)) testutils.Equals(tt, date, string(mclass.Date)) testutils.Equals(tt, 0, len(mclass.Class)) } } t.Run("agent", next("Agent", "01.72.11582", "2016-03-29T21:50:11")) t.Run("offfice", next("Office", "01.72.11583", "2016-03-29T21:50:11")) t.Run("listing", next("Listing", "01.72.11584", "2016-03-29T21:50:11")) err = extractor.DecodeNext("METADATA-CLASS", &MClass{}) testutils.Equals(t, io.EOF, err) }
func TestSearchCompactBadChar(t *testing.T) { rets := `<?xml version="1.0" encoding="UTF-8" ?> <RETS ReplyCode="0" ReplyText="Operation Successful"> <COUNT Records="1" /> <DELIMITER value = "09"/> <COLUMNS> A B C D E F </COLUMNS> <DATA> 1` + "\x0b" + `1 2 3 4 6 </DATA> </RETS>` body := ioutil.NopCloser(strings.NewReader(rets)) cr, err := NewCompactSearchResult(body) testutils.Ok(t, err) testutils.Equals(t, StatusOK, cr.Response.Code) counter := 0 cr.ForEach(func(row Row, err error) error { testutils.Ok(t, err) testutils.Equals(t, "1 1,2,3,4,,6", strings.Join(row, ",")) counter = counter + 1 return nil }) }
func TestSearchXMLBadChar(t *testing.T) { type Listing struct { Row string } rets := `<?xml version="1.0" encoding="UTF-8" ?> <RETS ReplyCode="0" ReplyText="Operation Successful"> <COUNT Records="5" /> <Listings> <PropertyListing> <Row>bad` + "\x0b" + `row</Row> </PropertyListing> <PropertyListing> <Row>good row</Row> </PropertyListing> </Listings> <MAXROWS/> </RETS>` body := ioutil.NopCloser(strings.NewReader(rets)) cr, err := NewStandardXMLSearchResult(body) testutils.Ok(t, err) testutils.Equals(t, StatusOK, cr.Response.Code) var listings []Listing count, maxRows, err := cr.ForEach(minidom.ByName("PropertyListing"), func(elem io.ReadCloser, err error) error { listing := Listing{} xml.NewDecoder(elem).Decode(&listing) listings = append(listings, listing) return err }) testutils.Ok(t, err) testutils.Equals(t, true, maxRows) testutils.Equals(t, 5, count) testutils.Equals(t, 2, len(listings)) testutils.Equals(t, "bad row", listings[0].Row) testutils.Equals(t, "good row", listings[1].Row) }
func TestLoad(t *testing.T) { body := ioutil.NopCloser(strings.NewReader(raw)) defer body.Close() parser := xml.NewDecoder(body) xml := RETSResponseWrapper{} err := parser.Decode(&xml) if err != io.EOF { testutils.Ok(t, err) } testutils.Equals(t, "Operation Successful", xml.ReplyText) testutils.Equals(t, "ABBA", xml.Metadata.MSystem.System.ID) testutils.Equals(t, "Property", string(xml.Metadata.MSystem.System.MResource.Resource[0].ResourceID)) }
func TestSearchXMLParseSearchQuit(t *testing.T) { noEnd := strings.Split(standardXML, "Commercial")[0] body := ioutil.NopCloser(strings.NewReader(noEnd)) cr, err := NewStandardXMLSearchResult(body) testutils.Ok(t, err) var listings [][]byte count, maxRows, err := cr.ForEach(minidom.ByName("PropertyListing"), func(elem io.ReadCloser, err error) error { tmp, _ := ioutil.ReadAll(elem) listings = append(listings, tmp) return err }) testutils.NotOk(t, err) testutils.Equals(t, false, maxRows) testutils.Equals(t, 10, count) testutils.Equals(t, 1, len(listings)) }
func TestSearchCompactParseSearchQuit(t *testing.T) { noEnd := strings.Split(compactDecoded, "<MAXROWS/>")[0] body := ioutil.NopCloser(strings.NewReader(noEnd)) cr, err := NewCompactSearchResult(body) testutils.Ok(t, err) rowsFound := 0 cr.ForEach(func(data Row, err error) error { if err != nil { testutils.Assert(t, strings.Contains(err.Error(), "EOF"), "found something not eof") return err } testutils.Equals(t, "1,2,3,4,,6", strings.Join(data, ",")) rowsFound++ return nil }) testutils.Equals(t, 8, rowsFound) }
func TestGetObject(t *testing.T) { header := http.Header{} textproto.MIMEHeader(header).Add("Content-Type", "image/jpeg") textproto.MIMEHeader(header).Add("Content-ID", "123456") textproto.MIMEHeader(header).Add("Object-ID", "1") textproto.MIMEHeader(header).Add("Preferred", "1") textproto.MIMEHeader(header).Add("UID", "1a234234234") textproto.MIMEHeader(header).Add("Content-Description", "Outhouse") textproto.MIMEHeader(header).Add("Content-Sub-Description", "The urinal") textproto.MIMEHeader(header).Add("Location", "http://www.simpleboundary.com/image-5.jpg") var body = `<binary data 1>` response := GetObjectResponse{ response: &http.Response{ Header: header, Body: ioutil.NopCloser(strings.NewReader(body)), }, } defer response.Close() var objects []*Object err := response.ForEach(func(o *Object, err error) error { objects = append(objects, o) return nil }) testutils.Ok(t, err) testutils.Equals(t, 1, len(objects)) o := objects[0] testutils.Equals(t, true, o.Preferred) testutils.Equals(t, "image/jpeg", o.ContentType) testutils.Equals(t, "123456", o.ContentID) testutils.Equals(t, 1, o.ObjectID) testutils.Equals(t, "1a234234234", o.UID) testutils.Equals(t, "Outhouse", o.Description) testutils.Equals(t, "The urinal", o.SubDescription) testutils.Equals(t, "<binary data 1>", string(o.Blob)) testutils.Equals(t, "http://www.simpleboundary.com/image-5.jpg", o.Location) testutils.Equals(t, false, o.RetsError) }
func TestParseCapabilitiesRelativeUrls(t *testing.T) { body := `<RETS ReplyCode="0" ReplyText="V2.7.0 2315: Success"> <RETS-RESPONSE> MemberName=Threewide Corporation User=2000343, Association Member Primary:Login:Media Restrictions:Office:RETS:RETS Advanced:RETS Customer:System-MRIS:MDS Access Common:MDS Application Login, 90, TURD1 Broker=TWD,1 MetadataVersion=1.12.30 MinMetadataVersion=1.1.1 OfficeList=TWD;1 TimeoutSeconds=1800 Login=/platinum/login Action=/platinum/get?Command=Message Search=/platinum/search Get=/platinum/get GetObject=/platinum/getobject Logout=/platinum/logout GetMetadata=/platinum/getmetadata ChangePassword=/platinum/changepassword X-SampleLinks=/rets2_2/Links X-SupportSite=http://flexmls.com/rets/ X-NotificationFeed=http://example.com/atom/feed/private/atom.xml </RETS-RESPONSE> </RETS>` urls, err := parseCapability( ioutil.NopCloser(strings.NewReader(body)), "http://server.com:6103/platinum/login", ) testutils.Ok(t, err) testutils.Equals(t, urls.Response.Text, "V2.7.0 2315: Success") testutils.Equals(t, urls.Response.Code, StatusOK) testutils.Equals(t, urls.Login, "http://server.com:6103/platinum/login") testutils.Equals(t, urls.GetMetadata, "http://server.com:6103/platinum/getmetadata") testutils.Equals(t, "http://server.com:6103/rets2_2/Links", urls.AdditionalURLs["X-SampleLinks"]) }
func TestClass(t *testing.T) { var raw = `<?xml version="1.0" encoding="utf-8"?> <RETS ReplyCode="0" ReplyText="Operation Successful"> <METADATA> <METADATA-CLASS Version="01.72.11588" Date="2016-06-01T16:05:01" Resource="Property"> <Class> <ClassName>COMM</ClassName> <StandardName>CommercialSale</StandardName> <VisibleName>Commercial</VisibleName> <Description>Contains data for Commercial searches.</Description> <TableVersion>01.72.11581</TableVersion> <TableDate>2016-02-09T06:02:17</TableDate> <UpdateVersion>01.72.10221</UpdateVersion> <UpdateDate/> <ClassTimeStamp>LastModifiedDateTime</ClassTimeStamp> <DeletedFlagField/> <DeletedFlagValue/> <HasKeyIndex>0</HasKeyIndex> <ColumnGroupVersion>01.72.11581</ColumnGroupVersion> <ColumnGroupDate>2016-02-09T06:02:17</ColumnGroupDate> <ColumnGroupSetVersion>01.72.11581</ColumnGroupSetVersion> <ColumnGroupSetDate>2016-02-09T06:02:17</ColumnGroupSetDate> <METADATA-TABLE Version="01.72.11581" Date="2016-02-09T06:02:17" System="ANNA" Resource="Property" Class="COMM"> </METADATA-TABLE> </Class> <Class> <ClassName>LOTL</ClassName> <StandardName>Land</StandardName> </Class> </METADATA-CLASS> </METADATA> </RETS>` body := ioutil.NopCloser(strings.NewReader(raw)) defer body.Close() extractor := &Extractor{Body: body} response, err := extractor.Open() testutils.Ok(t, err) testutils.Equals(t, "Operation Successful", response.ReplyText) mclass := &MClass{} err = extractor.DecodeNext("METADATA-CLASS", mclass) testutils.Ok(t, err) testutils.Equals(t, "Property", string(mclass.Resource)) testutils.Equals(t, "01.72.11588", string(mclass.Version)) testutils.Equals(t, "2016-06-01T16:05:01", string(mclass.Date)) testutils.Equals(t, 2, len(mclass.Class)) comm := mclass.Class[0] testutils.Equals(t, "COMM", string(comm.ClassName)) testutils.Equals(t, "CommercialSale", string(comm.StandardName)) testutils.Equals(t, "Commercial", string(comm.VisibleName)) testutils.Equals(t, "Contains data for Commercial searches.", string(comm.Description)) testutils.Equals(t, "01.72.11581", string(comm.TableVersion)) testutils.Equals(t, "2016-02-09T06:02:17", string(comm.TableDate)) // TODO fill in the rest when time permits lotl := mclass.Class[1] testutils.Equals(t, "LOTL", string(lotl.ClassName)) testutils.Equals(t, "Land", string(lotl.StandardName)) }
func TestGetObjects(t *testing.T) { headers := http.Header{} headers.Add("Content-Type", contentType) response := GetObjectResponse{ response: &http.Response{ Header: headers, Body: ioutil.NopCloser(strings.NewReader(multipartBody)), }, } defer response.Close() var objects []*Object response.ForEach(func(o *Object, err error) error { testutils.Ok(t, err) objects = append(objects, o) return nil }) o1 := objects[0] testutils.Equals(t, true, o1.Preferred) testutils.Equals(t, "image/jpeg", o1.ContentType) testutils.Equals(t, "123456", o1.ContentID) testutils.Equals(t, 1, o1.ObjectID) testutils.Equals(t, "<binary data 1>", string(o1.Blob)) testutils.Equals(t, "123456", o1.ObjectData["ListingKey"]) testutils.Equals(t, "2013-05-01T12:34:34.8-0500", o1.ObjectData["ListDate"]) o2 := objects[1] testutils.Equals(t, 2, o2.ObjectID) testutils.Equals(t, "1a234234234", o2.UID) o3 := objects[2] testutils.Equals(t, 3, o3.ObjectID) testutils.Equals(t, "Outhouse", o3.Description) testutils.Equals(t, "The urinal", o3.SubDescription) o4 := objects[3] testutils.Equals(t, true, o4.RetsError) testutils.Equals(t, "text/xml", o4.ContentType) testutils.Equals(t, "There is no object with that Object-ID", o4.RetsMessage.Text) testutils.Equals(t, StatusObjectNotFound, o4.RetsMessage.Code) o5 := objects[4] testutils.Equals(t, "http://www.simpleboundary.com/image-5.jpg", o5.Location) testutils.Equals(t, "image/jpeg", o5.ContentType) testutils.Equals(t, "123457", o5.ContentID) testutils.Equals(t, 5, o5.ObjectID) testutils.Equals(t, "", string(o5.Blob)) o6 := objects[5] testutils.Equals(t, "http://www.simpleboundary.com/image-6.jpg", o6.Location) testutils.Equals(t, "image/jpeg", o6.ContentType) testutils.Equals(t, "123457", o6.ContentID) testutils.Equals(t, 6, o6.ObjectID) testutils.Equals(t, "<binary data 6>", string(o6.Blob)) testutils.Assert(t, o6.RetsMessage == nil, "should not be the zerod object") o7 := objects[6] testutils.Equals(t, "http://www.simpleboundary.com/image-7.jpg", o7.Location) testutils.Equals(t, "image/jpeg", o7.ContentType) testutils.Equals(t, "123457", o7.ContentID) testutils.Equals(t, 7, o7.ObjectID) testutils.Equals(t, "", string(o7.Blob)) testutils.Equals(t, "Found it!", o7.RetsMessage.Text) }