diff --git a/project/build/ContentApiClient.scala b/project/build/ContentApiClient.scala index a10d88c..ba73da4 100644 --- a/project/build/ContentApiClient.scala +++ b/project/build/ContentApiClient.scala @@ -4,6 +4,8 @@ class ContentApiClient(info: ProjectInfo) extends DefaultProject(info) with Idea val joda = "joda-time" % "joda-time" % "1.6" withSources() val httpClient = "commons-httpclient" % "commons-httpclient" % "3.1" - + + val lift_json = "net.liftweb" %% "lift-json" % "2.1-M1" withSources() + val scalaTest = "org.scalatest" % "scalatest" % "1.2" % "test" withSources() } diff --git a/src/main/scala/com/gu/openplatform/contentapi/model/Model.scala b/src/main/scala/com/gu/openplatform/contentapi/model/Model.scala index 7236ba5..e824437 100644 --- a/src/main/scala/com/gu/openplatform/contentapi/model/Model.scala +++ b/src/main/scala/com/gu/openplatform/contentapi/model/Model.scala @@ -3,6 +3,7 @@ package com.gu.openplatform.contentapi.model import org.joda.time.DateTime import java.net.URL + class Response( val format: String, val status: String, diff --git a/src/main/scala/com/gu/openplatform/contentapi/model/NewModel.scala b/src/main/scala/com/gu/openplatform/contentapi/model/NewModel.scala new file mode 100644 index 0000000..3fed555 --- /dev/null +++ b/src/main/scala/com/gu/openplatform/contentapi/model/NewModel.scala @@ -0,0 +1,112 @@ +package com.gu.openplatform.contentapi.model.json + +import org.joda.time.DateTime + +// NB: +// Due to the way lift-json parses lists and collections +// we have to be inconsitant with optional lists. +// Everywhere else where things are optional, we make then Options. +// But for lists, this doesn't work (you get back Some(Nil) even if +// the element is not present. +// So Lists are never Option[List], you will just get back Nil if the +// the element was not present in the response + + +// Responses + +// /search +case class SearchResponse( + status: String, + userTier: String, + total: Long, + startIndex: Long, + pageSize: Long, + currentPage: Long, + pages: Long, + orderBy: String, + results: List[Content], + refinementGroups: List[RefinementGroup] +) + +// /tags +case class TagsResponse( + status: String, + userTier: String, + total: Long, + startIndex: Long, + pageSize: Long, + currentPage: Long, + pages: Long, + results: List[Tag] +) + + + + + +// Model classes +case class Content( + // the id of this item of content: this should always be the path + // to the item on www.guardian.co.uk + id: String, + // section is usually provided: some content (such as user help information) + // does not belong to any section + sectionId: Option[String], + sectionName: Option[String], + webPublicationDate: DateTime, + webTitle: String, + webUrl: String, + apiUrl: String, + fields: Option[Map[String, String]], + tags: List[Tag], + factboxes: List[Factbox], + mediaAssets: List[MediaAsset] +) + + +case class Tag( + id: String, + `type` : String, + sectionId: Option[String], + sectionName: Option[String], + webTitle: String, + webUrl: String, + apiUrl: String + ) { + // for those that don't like backticks + def tagType = `type` +} + +case class Factbox( + `type`: String, + heading: Option[String], + picture: Option[String], + fields: Option[Map[String, String]] + ) { + def factboxType = `type` +} + +case class MediaAsset( + `type`: String, + rel: String, + index: Int, + file: String, + fields: Option[Map[String, String]] + ) { + def mediaAssetType = `type` +} + +case class RefinementGroup( + `type`: String, + refinements: List[Refinement] + ) { + def refinementType = `type` +} + +case class Refinement( + count: Long, + refinedUrl: String, + displayName: String, + id: String, + apiUrl: String + ) diff --git a/src/main/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializer.scala b/src/main/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializer.scala new file mode 100644 index 0000000..04780f1 --- /dev/null +++ b/src/main/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializer.scala @@ -0,0 +1,24 @@ +package com.gu.openplatform.contentapi.parser + +import org.joda.time.DateTime +import scala.PartialFunction +import net.liftweb.json.JsonAST._ +import org.joda.time.format.ISODateTimeFormat +import net.liftweb.json.{MappingException, TypeInfo, Formats, Serializer} + +class JodaJsonSerializer extends Serializer[DateTime] { + private val DateTimeClass = classOf[DateTime] + val formatter = ISODateTimeFormat.dateTimeNoMillis + + def deserialize(implicit format: Formats): PartialFunction[(TypeInfo, JValue), DateTime] = { + case (TypeInfo(DateTimeClass, _), json) => json match { + case JString(s) => formatter.parseDateTime(s) + case x => throw new MappingException("Can't convert " + x + " to DateTime") + } + } + + def serialize(implicit format: Formats): PartialFunction[Any, JValue] = { + case dt: DateTime => JString(formatter.print(dt)) + } + +} diff --git a/src/main/scala/com/gu/openplatform/contentapi/parser/JsonParser.scala b/src/main/scala/com/gu/openplatform/contentapi/parser/JsonParser.scala new file mode 100644 index 0000000..9de4e1d --- /dev/null +++ b/src/main/scala/com/gu/openplatform/contentapi/parser/JsonParser.scala @@ -0,0 +1,17 @@ +package com.gu.openplatform.contentapi.parser + +import scala.xml._ +import org.joda.time.format.ISODateTimeFormat +import com.gu.openplatform.contentapi.model.json._ +import java.net.URL +import net.liftweb.json.JsonParser._ +import net.liftweb.json.JsonAST.JValue + + +class LiftJsonParser { + implicit val formats = net.liftweb.json.DefaultFormats + new JodaJsonSerializer + + def parseSearch(json: String) = (parse(json) \ "response").extract[SearchResponse] + def parseTags(json: String) = (parse(json) \ "response").extract[TagsResponse] + +} \ No newline at end of file diff --git a/src/main/scala/com/gu/openplatform/contentapi/parser/XMLParser.scala b/src/main/scala/com/gu/openplatform/contentapi/parser/XmlParser.scala similarity index 100% rename from src/main/scala/com/gu/openplatform/contentapi/parser/XMLParser.scala rename to src/main/scala/com/gu/openplatform/contentapi/parser/XmlParser.scala diff --git a/src/test/resources/com/gu/openplatform/contentapi/parser/search-partner.json b/src/test/resources/com/gu/openplatform/contentapi/parser/search-partner.json new file mode 100644 index 0000000..21436ec --- /dev/null +++ b/src/test/resources/com/gu/openplatform/contentapi/parser/search-partner.json @@ -0,0 +1,56 @@ +{ + "response":{ + "status":"ok", + "userTier":"partner", + "total":1, + "startIndex":1, + "pageSize":2, + "currentPage":1, + "pages":1, + "orderBy":"newest", + "results":[{ + "id":"music/picture/2010/aug/25/georgemichael-ukcrime", + "sectionId":"music", + "sectionName":"Music", + "webPublicationDate":"2010-08-25T11:08:53+01:00", + "webTitle":"Eyewitness: Wham! rap", + "webUrl":"http://www.guardian.co.uk/music/picture/2010/aug/25/georgemichael-ukcrime", + "apiUrl":"http://content.guardianapis.com/music/picture/2010/aug/25/georgemichael-ukcrime", + "factboxes":[{ + "type":"photography-tip", + "fields":{ + "proTip":"By using auto-focus, continuous shooting mode and holding his camera above the crowd, the photographer has managed to capture the scene" + } + }], + "mediaAssets":[{ + "type":"picture", + "rel":"body", + "index":1, + "file":"http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2010/8/25/1282722354152/george-michael-appears-in-003.jpg", + "fields":{ + "source":"Getty Images", + "photographer":"Peter Macdiarmid", + "height":"519", + "credit":"Peter Macdiarmid/Getty Images", + "altText":"george michael appears in court charged with driving offences", + "caption":"Singer George Michael leaves Highbury magistrates court surrounded by press and police. Michael pleaded guilty to driving under the influence of drugs and possessing cannabis after he crashed his car into a shop in London. The singer has been warned that he may face a custodial sentence after a previous similar offence", + "width":"780" + } + },{ + "type":"picture", + "rel":"big-picture", + "index":1, + "file":"http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2010/8/25/1282722355694/george-michael-appears-in-004.jpg", + "fields":{ + "source":"Getty Images", + "photographer":"Peter Macdiarmid", + "height":"768", + "credit":"Peter Macdiarmid/Getty Images", + "altText":"george michael appears in court charged with driving offences", + "caption":"Singer George Michael leaves Highbury magistrates court surrounded by press and police. Michael pleaded guilty to driving under the influence of drugs and possessing cannabis after he crashed his car into a shop in London. The singer has been warned that he may face a custodial sentence after a previous similar offence", + "width":"1024" + } + }] + }] + } +} \ No newline at end of file diff --git a/src/test/resources/com/gu/openplatform/contentapi/parser/search.json b/src/test/resources/com/gu/openplatform/contentapi/parser/search.json new file mode 100644 index 0000000..e461ff8 --- /dev/null +++ b/src/test/resources/com/gu/openplatform/contentapi/parser/search.json @@ -0,0 +1,224 @@ +{ + "response":{ + "status":"ok", + "userTier":"free", + "total":1210879, + "startIndex":1, + "pageSize":2, + "currentPage":1, + "pages":605440, + "orderBy":"newest", + "results":[{ + "id":"edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events", + "sectionId":"edinburgh", + "sectionName":"Edinburgh", + "webPublicationDate":"2010-08-27T10:30:05+01:00", + "webTitle":"Map: Things to do across Edinburgh this Bank Holiday weekend", + "webUrl":"http://www.guardian.co.uk/edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events", + "apiUrl":"http://content.guardianapis.com/edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events", + "fields":{ + "headline":"Map: Things to do across Edinburgh this Bank Holiday weekend", + "trailText":"

Explore events for all ages going on in Edinburgh

", + "shortUrl":"http://gu.com/p/2javn", + "standfirst":"Explore events for all ages going on in Edinburgh", + "byline":"Michael MacLeod", + "publication":"guardian.co.uk" + }, + "tags":[{ + "id":"edinburgh/edinburgh", + "type":"blog", + "webTitle":"Edinburgh", + "webUrl":"http://www.guardian.co.uk/edinburgh/edinburgh", + "apiUrl":"http://content.guardianapis.com/edinburgh/edinburgh", + "sectionId":"edinburgh", + "sectionName":"Edinburgh" + },{ + "id":"tone/blog", + "type":"tone", + "webTitle":"Blogposts", + "webUrl":"http://www.guardian.co.uk/tone/blog", + "apiUrl":"http://content.guardianapis.com/tone/blog" + },{ + "id":"profile/michael-macleod", + "type":"contributor", + "webTitle":"Michael MacLeod", + "webUrl":"http://www.guardian.co.uk/profile/michael-macleod", + "apiUrl":"http://content.guardianapis.com/profile/michael-macleod" + },{ + "id":"type/article", + "type":"type", + "webTitle":"Article", + "webUrl":"http://www.guardian.co.uk/articles", + "apiUrl":"http://content.guardianapis.com/articles" + }] + },{ + "id":"football/2010/aug/27/newcastle-steven-taylor-hatem-ben-arfa", + "sectionId":"football", + "sectionName":"Football", + "webPublicationDate":"2010-08-27T10:26:22+01:00", + "webTitle":"Newcastle hopeful over Steven Taylor and Hatem Ben Arfa deals", + "webUrl":"http://www.guardian.co.uk/football/2010/aug/27/newcastle-steven-taylor-hatem-ben-arfa", + "apiUrl":"http://content.guardianapis.com/football/2010/aug/27/newcastle-steven-taylor-hatem-ben-arfa", + "fields":{ + "headline":"Newcastle hopeful over Steven Taylor and Hatem Ben Arfa deals", + "trailText":"Chris Hughton hopes Steven Taylor will stay and says talks are ongoing about bringing Hatem Ben Arfa to the club", + "shortUrl":"http://gu.com/p/2ja2h", + "standfirst":"\u2022 Chris Hughton confident Steven Taylor will stay
\u2022 Talks with Marseille over Hatem Ben Arfa 'ongoing'", + "thumbnail":"http://static.guim.co.uk/sys-images/Football/Pix/pictures/2010/3/27/1269708526219/Steven-Taylor-005.jpg", + "byline":"Press Association", + "publication":"guardian.co.uk" + }, + "tags":[{ + "id":"football/newcastleunited", + "type":"keyword", + "webTitle":"Newcastle United", + "webUrl":"http://www.guardian.co.uk/football/newcastleunited", + "apiUrl":"http://content.guardianapis.com/football/newcastleunited", + "sectionId":"football", + "sectionName":"Football" + },{ + "id":"football/football", + "type":"keyword", + "webTitle":"Football", + "webUrl":"http://www.guardian.co.uk/football/football", + "apiUrl":"http://content.guardianapis.com/football/football", + "sectionId":"football", + "sectionName":"Football" + },{ + "id":"football/transfer-window", + "type":"keyword", + "webTitle":"Transfer window", + "webUrl":"http://www.guardian.co.uk/football/transfer-window", + "apiUrl":"http://content.guardianapis.com/football/transfer-window", + "sectionId":"football", + "sectionName":"Football" + },{ + "id":"sport/sport", + "type":"keyword", + "webTitle":"Sport", + "webUrl":"http://www.guardian.co.uk/sport/sport", + "apiUrl":"http://content.guardianapis.com/sport/sport", + "sectionId":"sport", + "sectionName":"Sport" + },{ + "id":"tone/news", + "type":"tone", + "webTitle":"News", + "webUrl":"http://www.guardian.co.uk/tone/news", + "apiUrl":"http://content.guardianapis.com/tone/news" + },{ + "id":"type/article", + "type":"type", + "webTitle":"Article", + "webUrl":"http://www.guardian.co.uk/articles", + "apiUrl":"http://content.guardianapis.com/articles" + }] + }], + "refinementGroups":[{ + "type":"keyword", + "refinements":[{ + "count":256463, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=uk/uk", + "displayName":"UK news", + "id":"uk/uk", + "apiUrl":"http://content.guardianapis.com/uk/uk" + },{ + "count":207418, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=world/world", + "displayName":"World news", + "id":"world/world", + "apiUrl":"http://content.guardianapis.com/world/world" + }] + },{ + "type":"type", + "refinements":[{ + "count":1167622, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=type/article", + "displayName":"Article", + "id":"type/article", + "apiUrl":"http://content.guardianapis.com/articles" + },{ + "count":10838, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=type/gallery", + "displayName":"Gallery", + "id":"type/gallery", + "apiUrl":"http://content.guardianapis.com/inpictures" + }] + },{ + "type":"contributor", + "refinements":[{ + "count":8300, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=profile/jackschofield", + "displayName":"Jack Schofield", + "id":"profile/jackschofield", + "apiUrl":"http://content.guardianapis.com/profile/jackschofield" + },{ + "count":6724, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=profile/roygreenslade", + "displayName":"Roy Greenslade", + "id":"profile/roygreenslade", + "apiUrl":"http://content.guardianapis.com/profile/roygreenslade" + }] + },{ + "type":"tone", + "refinements":[{ + "count":126633, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=tone/news", + "displayName":"News", + "id":"tone/news", + "apiUrl":"http://content.guardianapis.com/tone/news" + },{ + "count":110283, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=tone/comment", + "displayName":"Comment", + "id":"tone/comment", + "apiUrl":"http://content.guardianapis.com/tone/comment" + }] + },{ + "type":"section", + "refinements":[{ + "count":125848, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2§ion=world&show-fields=all&show-refinements=all&show-tags=all", + "displayName":"World news", + "id":"world", + "apiUrl":"http://content.guardianapis.com/world" + },{ + "count":94898, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2§ion=sport&show-fields=all&show-refinements=all&show-tags=all", + "displayName":"Sport", + "id":"sport", + "apiUrl":"http://content.guardianapis.com/sport" + }] + },{ + "type":"series", + "refinements":[{ + "count":3476, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=crosswords/series/quick", + "displayName":"Quick", + "id":"crosswords/series/quick", + "apiUrl":"http://content.guardianapis.com/crosswords/series/quick" + },{ + "count":3340, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=theguardian/series/correctionsandclarifications", + "displayName":"Corrections and clarifications", + "id":"theguardian/series/correctionsandclarifications", + "apiUrl":"http://content.guardianapis.com/theguardian/series/correctionsandclarifications" + }] + },{ + "type":"blog", + "refinements":[{ + "count":51780, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=commentisfree/commentisfree", + "displayName":"Comment is free", + "id":"commentisfree/commentisfree", + "apiUrl":"http://content.guardianapis.com/commentisfree/commentisfree" + },{ + "count":15109, + "refinedUrl":"http://content.guardianapis.com/search?format=json&page-size=2&refinement-size=2&show-fields=all&show-refinements=all&show-tags=all&tag=sport/blog", + "displayName":"Sportblog", + "id":"sport/blog", + "apiUrl":"http://content.guardianapis.com/sport/blog" + }] + }] + } +} \ No newline at end of file diff --git a/src/test/resources/com/gu/openplatform/contentapi/parser/tags.json b/src/test/resources/com/gu/openplatform/contentapi/parser/tags.json new file mode 100644 index 0000000..0448f40 --- /dev/null +++ b/src/test/resources/com/gu/openplatform/contentapi/parser/tags.json @@ -0,0 +1,26 @@ +{ + "response":{ + "status":"ok", + "userTier":"free", + "total":20733, + "startIndex":1, + "pageSize":2, + "currentPage":1, + "pages":10367, + "results":[{ + "id":"enjoy-england/south-west", + "type":"keyword", + "webTitle":"South-west", + "webUrl":"http://www.guardian.co.uk/enjoy-england/south-west", + "apiUrl":"http://content.guardianapis.com/enjoy-england/south-west", + "sectionId":"enjoy-england", + "sectionName":"Enjoy England" + },{ + "id":"profile/dick-vinegar", + "type":"contributor", + "webTitle":"Dick Vinegar", + "webUrl":"http://www.guardian.co.uk/profile/dick-vinegar", + "apiUrl":"http://content.guardianapis.com/profile/dick-vinegar" + }] + } +} \ No newline at end of file diff --git a/src/test/scala/com/gu/openplatform/contentapi/NewTest.scala b/src/test/scala/com/gu/openplatform/contentapi/NewTest.scala new file mode 100644 index 0000000..833939f --- /dev/null +++ b/src/test/scala/com/gu/openplatform/contentapi/NewTest.scala @@ -0,0 +1,32 @@ + +package com.gu.openplatform.contentapi + +import connection.Http +import model.json.{SearchResponse} +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.FeatureSpec + +import net.liftweb.json._ + +class NewTest extends FeatureSpec with ShouldMatchers { + + implicit val formats = net.liftweb.json.DefaultFormats + + feature("new json stuff") { + + scenario("get stuff") { + + val result = Http GET "http://content.guardianapis.com/search.json?show-tags=all" + + println(result.body) + + val json = JsonParser.parse(result.body) + + val parsed = (json \ "response").extract[SearchResponse] + + println(parsed) + + } + } +} + diff --git a/src/test/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializerTest.scala b/src/test/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializerTest.scala new file mode 100644 index 0000000..fe3f234 --- /dev/null +++ b/src/test/scala/com/gu/openplatform/contentapi/parser/JodaJsonSerializerTest.scala @@ -0,0 +1,22 @@ +package com.gu.openplatform.contentapi.parser + +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.FlatSpec +import net.liftweb.json.JsonParser._ +import net.liftweb.json.JsonAST._ +import org.joda.time.{DateTimeZone, DateTime} + + +class JodaJsonSerializerTest extends FlatSpec with ShouldMatchers { + implicit val formats = net.liftweb.json.DefaultFormats + new JodaJsonSerializer + + case class SimpleTest(dt: DateTime) + + val jsonString = """ { "dt":"2010-08-27T10:30:05+01:00" } """ + + "joda serializer" should "deserialize date time" in { + val json = parse(jsonString) + val result = json.extract[SimpleTest] + result.dt should be(new DateTime(2010, 8, 27, 10, 30, 5, 0)) + } +} \ No newline at end of file diff --git a/src/test/scala/com/gu/openplatform/contentapi/parser/JsonFileLoader.scala b/src/test/scala/com/gu/openplatform/contentapi/parser/JsonFileLoader.scala new file mode 100644 index 0000000..6b2eec3 --- /dev/null +++ b/src/test/scala/com/gu/openplatform/contentapi/parser/JsonFileLoader.scala @@ -0,0 +1,12 @@ +package com.gu.openplatform.contentapi.parser + +import io.Source + +object JsonFileLoader { + def loadFile(filename: String) = { + Option(getClass.getResourceAsStream(filename)) + .map(Source.fromInputStream(_, "UTF-8").mkString) + .getOrElse(error("could not load file " + filename)) + } + +} \ No newline at end of file diff --git a/src/test/scala/com/gu/openplatform/contentapi/parser/SearchJsonParserTest.scala b/src/test/scala/com/gu/openplatform/contentapi/parser/SearchJsonParserTest.scala new file mode 100644 index 0000000..80f6a21 --- /dev/null +++ b/src/test/scala/com/gu/openplatform/contentapi/parser/SearchJsonParserTest.scala @@ -0,0 +1,170 @@ +package com.gu.openplatform.contentapi.parser + +import org.scalatest.matchers.ShouldMatchers +import com.gu.openplatform.contentapi.model.json._ +import org.scalatest.FlatSpec +import org.joda.time.DateTime +import JsonFileLoader._ + +class SearchJsonParserTest extends FlatSpec with ShouldMatchers { + + val parser = new LiftJsonParser + + // generated by: + // http://content.guardianapis.com/search.json + // ?page-size=2&show-fields=all&show-tags=all&show-refinements=all&refinement-size=2 + lazy val searchResponse = parser.parseSearch(loadFile("search.json")) + + // generated by: + // http://content.guardianapis.com/search.json + // ?api-key=&ids=music/picture/2010/aug/25/georgemichael-ukcrime&show-media=all&show-factboxes=all + lazy val partnerSearchResponse = parser.parseSearch(loadFile("search-partner.json")) + + "search endpoint parser" should "parse basic reponse header" in { + searchResponse.status should be ("ok") + searchResponse.userTier should be ("free") + } + + it should "parse pagination" in { + searchResponse.startIndex should be (1) + searchResponse.pageSize should be (2) + searchResponse.currentPage should be (1) + searchResponse.pages should be (605440) + searchResponse.total should be (1210879) + } + + it should "have parse content correctly" in { + searchResponse.results.size should be (2) + val content = searchResponse.results.head + content.id should be ("edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events") + content.sectionId should be (Some("edinburgh")) + content.sectionName should be (Some("Edinburgh")) + content.webPublicationDate should be (new DateTime(2010, 8, 27, 10, 30, 05, 0)) + content.webTitle should be ("Map: Things to do across Edinburgh this Bank Holiday weekend") + content.webUrl should be ("http://www.guardian.co.uk/edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events") + content.apiUrl should be ("http://content.guardianapis.com/edinburgh/2010/aug/27/edinburgh-map-bank-holiday-events") + } + + it should "parse fields" in { + val fields = searchResponse.results.head.fields + + fields should be (Some(Map( + "headline" -> "Map: Things to do across Edinburgh this Bank Holiday weekend", + "trailText" -> "

Explore events for all ages going on in Edinburgh

", + "shortUrl" -> "http://gu.com/p/2javn", + "standfirst" -> "Explore events for all ages going on in Edinburgh", + "byline" -> "Michael MacLeod", + "publication" -> "guardian.co.uk" + ))) + } + + it should "parse tags" in { + val tags = searchResponse.results.head.tags + + tags.size should be (4) + + val edinburgh = tags.head + edinburgh.id should be ("edinburgh/edinburgh") + edinburgh.tagType should be ("blog") + edinburgh.webTitle should be ("Edinburgh") + edinburgh.webUrl should be ("http://www.guardian.co.uk/edinburgh/edinburgh") + edinburgh.apiUrl should be ("http://content.guardianapis.com/edinburgh/edinburgh") + edinburgh.sectionId should be (Some("edinburgh")) + edinburgh.sectionName should be (Some("Edinburgh")) + + // and check optional sections work + tags(1).sectionId should be (None) + } + + it should "parse refinement groups" in { + val refinements = searchResponse.refinementGroups + + refinements.size should be (7) + val keywords = refinements.head + keywords.`type` should be ("keyword") + keywords.refinements.size should be (2) + + keywords.refinements.head.count should be (256463) + keywords.refinements.head.displayName should be ("UK news") + } + + it should "parse factboxes" in { + searchResponse.results.head.factboxes should be (Nil) + val factboxes = partnerSearchResponse.results.head.factboxes + + factboxes.size should be (1) + factboxes.head.`type` should be ("photography-tip") + factboxes.head.fields should be (Some(Map( + "proTip" -> "By using auto-focus, continuous shooting mode and holding his camera above the crowd, the photographer has managed to capture the scene" + ))) + } + + it should "parse media assets" in { + searchResponse.results.head.mediaAssets should be (Nil) + + val mediaAssets = partnerSearchResponse.results.head.mediaAssets + mediaAssets.size should be (2) + mediaAssets.head.`type` should be ("picture") + mediaAssets.head.file should be ("http://static.guim.co.uk/sys-images/Guardian/Pix/pictures/2010/8/25/1282722354152/george-michael-appears-in-003.jpg") + + mediaAssets.head.fields should be (Some(Map( + "source" -> "Getty Images", + "photographer" -> "Peter Macdiarmid", + "height" -> "519", + "credit" -> "Peter Macdiarmid/Getty Images", + "altText" -> "george michael appears in court charged with driving offences", + "caption" -> "Singer George Michael leaves Highbury magistrates court surrounded by press and police. Michael pleaded guilty to driving under the influence of drugs and possessing cannabis after he crashed his car into a shop in London. The singer has been warned that he may face a custodial sentence after a previous similar offence", + "width" -> "780" + ))) + + + } + + + + + + /* + scenariosFor( + testBasicResponseHeaderCanBeParsed("tag endpoint", "tags", tagEndpointXml), + testPagedResponseHeaderCanBeParsed("tag endpoint", "tags", tagEndpointXml), + testThereAre2ItemsInTheResults("tag endpoint response -> results", "tags", tagEndpointXml, _.asInstanceOf[TagsResponse].results), + testTheStandardTagIsParsedCorrectly("tag endpoint response -> results -> tag", "tags", tagEndpointXml, _.asInstanceOf[TagsResponse].results.head) + ) + + scenariosFor( + testBasicResponseHeaderCanBeParsed("section endpoint", "sections", sectionEndpointXml), + testThereAre2ItemsInTheResults("section endpoint response -> results", "sections", sectionEndpointXml, _.asInstanceOf[SectionsResponse].results), + testTheStandardSectionIsParsedCorrectly("section endpoint response -> results -> section", "sections", sectionEndpointXml, _.asInstanceOf[SectionsResponse].results.head) + ) + + scenariosFor( + testBasicResponseHeaderCanBeParsed("id endpoint returning content", "id", idEndpointContentXml), + testTheStandardArticleIsParsedCorrectly("id endpoint", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get), + testThereAre2ItemsInTheResults("id endpoint response -> content -> tags", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get.tags), + testTheStandardTagIsParsedCorrectly("id endpoint response -> content -> tag", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get.tags.head), + testTheStandardFieldsAreParsedCorrectly("id endpoint", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get), + testTheStandardFactboxIsParsedCorrectly("id endpoint", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get.factboxes.head), + testTheStandardMediaAssetIsParsedCorrectly("id endpoint -> content -> media-assets", "id", idEndpointContentXml, _.asInstanceOf[ItemResponse].content.get.mediaAssets.head) + ) + + scenariosFor( + testBasicResponseHeaderCanBeParsed("id endpoint returning tag", "id", idEndpointTagXml), + testTheStandardTagIsParsedCorrectly("id endpoint response -> tag", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].tag.get), + testPagedItemResponseHeaderCanBeParsed("id endpoint returning tag", "id", idEndpointTagXml), + testThereAre2ItemsInTheResults("id endpoint returning tag -> results ", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].results), + testTheStandardArticleIsParsedCorrectly("id endpoint returning tag -> results", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].results.head), + testThereAre2ItemsInTheResults("id endpoint returning tag -> results -> tags", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].results.head.tags), + testTheStandardTagIsParsedCorrectly("id endpoint returning tag -> results -> content -> tag", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].results.head.tags.head), + testTheStandardFieldsAreParsedCorrectly("id endpoint returning tag -> results", "id", idEndpointTagXml, _.asInstanceOf[ItemResponse].results.head) + ) + + scenariosFor( + testBasicResponseHeaderCanBeParsed("id endpoint returning section", "id", idEndpointSectionXml), + testPagedItemResponseHeaderCanBeParsed("id endpoint returning section", "id", idEndpointSectionXml), + testTheStandardSectionIsParsedCorrectly("id endpoint response -> section", "id", idEndpointSectionXml, _.asInstanceOf[ItemResponse].section.get) + ) + */ + + +} \ No newline at end of file diff --git a/src/test/scala/com/gu/openplatform/contentapi/parser/TagJsonParserTest.scala b/src/test/scala/com/gu/openplatform/contentapi/parser/TagJsonParserTest.scala new file mode 100644 index 0000000..ee1519b --- /dev/null +++ b/src/test/scala/com/gu/openplatform/contentapi/parser/TagJsonParserTest.scala @@ -0,0 +1,33 @@ +package com.gu.openplatform.contentapi.parser + +import org.scalatest.matchers.ShouldMatchers +import org.scalatest.FlatSpec +import JsonFileLoader._ + +class TagJsonParserTest extends FlatSpec with ShouldMatchers { + + val parser = new LiftJsonParser + + // generated by: + // http://content.guardianapis.com/tags.json + // ?page-size=2&format=json + lazy val tagsResponse = parser.parseTags(loadFile("tags.json")) + + "tags endpoint parser" should "parse basic reponse header" in { + tagsResponse.status should be ("ok") + tagsResponse.userTier should be ("free") + } + + it should "parse pagination" in { + tagsResponse.startIndex should be (1) + tagsResponse.pageSize should be (2) + tagsResponse.currentPage should be (1) + tagsResponse.pages should be (10367) + tagsResponse.total should be (20733) + } + + it should "parse the tags" in { + tagsResponse.results.size should be (2) + tagsResponse.results.head.webTitle should be ("South-west") + } +} \ No newline at end of file