diff --git a/project/Settings.scala b/project/Settings.scala
index 27dda2c33..2f6edfdad 100644
--- a/project/Settings.scala
+++ b/project/Settings.scala
@@ -25,7 +25,7 @@ object Settings {
/** Declare global dependency versions here to avoid mismatches in multi part dependencies */
//noinspection ScalaStyle
object versions {
- val drtLib = "v993"
+ val drtLib = "v1009"
val scala = "2.13.12"
val scalaDom = "2.8.0"
diff --git a/server/src/main/resources/config/feeds.conf b/server/src/main/resources/config/feeds.conf
index a5ac77702..41a643fb4 100644
--- a/server/src/main/resources/config/feeds.conf
+++ b/server/src/main/resources/config/feeds.conf
@@ -72,6 +72,15 @@ feeds {
}
}
+ cwl {
+ iata {
+ endPointUrl = ""
+ endPointUrl = ${?CWL_IATA_ENDPOINT_URL}
+ username = ""
+ username = ${?CWL_IATA_USERNAME}
+ }
+ }
+
ltn {
live {
url = ${?LTN_LIVE_URL}
diff --git a/server/src/main/scala/actors/ProdDrtParameters.scala b/server/src/main/scala/actors/ProdDrtParameters.scala
index ac3e42451..7fae40859 100644
--- a/server/src/main/scala/actors/ProdDrtParameters.scala
+++ b/server/src/main/scala/actors/ProdDrtParameters.scala
@@ -27,6 +27,9 @@ trait DrtParameters {
val bhxIataUsername: String
val maybeBhxSoapEndPointUrl: Option[String]
+ val cwlIataEndPointUrl: String
+ val cwlIataUsername: String
+
val maybeLtnLiveFeedUrl: Option[String]
val maybeLtnLiveFeedUsername: Option[String]
val maybeLtnLiveFeedPassword: Option[String]
@@ -97,6 +100,9 @@ case class ProdDrtParameters @Inject()(config: Configuration) extends DrtParamet
override val bhxIataEndPointUrl: String = config.get[String]("feeds.bhx.iata.endPointUrl")
override val bhxIataUsername: String = config.get[String]("feeds.bhx.iata.username")
+ override val cwlIataEndPointUrl: String = config.get[String]("feeds.cwl.iata.endPointUrl")
+ override val cwlIataUsername: String = config.get[String]("feeds.cwl.iata.username")
+
override val maybeBhxSoapEndPointUrl: Option[String] = config.getOptional[String]("feeds.bhx.soap.endPointUrl")
override val maybeLtnLiveFeedUrl: Option[String] = config.getOptional[String]("feeds.ltn.live.url")
diff --git a/server/src/main/scala/drt/server/feeds/cwl/CWLFeed.scala b/server/src/main/scala/drt/server/feeds/cwl/CWLFeed.scala
index bf88280ad..59828a71c 100644
--- a/server/src/main/scala/drt/server/feeds/cwl/CWLFeed.scala
+++ b/server/src/main/scala/drt/server/feeds/cwl/CWLFeed.scala
@@ -1,13 +1,13 @@
package drt.server.feeds.cwl
import akka.actor.ActorSystem
-import akka.http.scaladsl.{ConnectionContext, Http}
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.unmarshalling.{FromResponseUnmarshaller, Unmarshal, Unmarshaller}
+import akka.http.scaladsl.{ConnectionContext, Http}
import akka.stream.Materializer
-import akka.stream.scaladsl.{Sink, Source}
+import akka.stream.scaladsl.Source
import akka.util.ByteString
import drt.server.feeds.Feed.FeedTick
import drt.server.feeds.{ArrivalsFeedFailure, ArrivalsFeedResponse, ArrivalsFeedSuccess}
@@ -22,8 +22,7 @@ import java.security.SecureRandom
import javax.net.ssl.SSLContext
import scala.collection.immutable
import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.{Await, Future}
-import scala.concurrent.duration.DurationInt
+import scala.concurrent.Future
import scala.util.Try
import scala.xml.{Node, NodeSeq}
@@ -115,8 +114,6 @@ trait CWLClientLike extends ScalaXmlSupport {
RawHeader("Accept-Encoding", "gzip,deflate"),
)
- println(s"Making request to $soapEndPoint\nHeaders: $headers\nPost XML: $postXml")
-
makeRequest(soapEndPoint, headers, postXml)
.map { res =>
log.info(s"Got a response from CWL ${res.status}")
@@ -124,7 +121,8 @@ trait CWLClientLike extends ScalaXmlSupport {
response.map {
case s: CWLFlightsResponseSuccess =>
- ArrivalsFeedSuccess(s.flights.map(fs => CWLFlight.portFlightToArrival(fs)))
+ val arrivals = s.flights.map(fs => CWLFlight.portFlightToArrival(fs))
+ ArrivalsFeedSuccess(arrivals)
case f: CWLFlightsResponseFailure =>
ArrivalsFeedFailure(f.message)
@@ -167,14 +165,8 @@ case class CWLClient(cwlLiveFeedUser: String, soapEndPoint: String) extends CWLC
val tls12Context = SSLContext.getInstance("TLSv1.2")
tls12Context.init(null, null, new SecureRandom())
- Http().singleRequest(request, connectionContext = ConnectionContext.httpsClient(tls12Context))
- // .map { r =>
- // val x = r.entity.getDataBytes().map(_.utf8String).asScala.runWith(Sink.fold("")(_ + _)).map { body =>
- // println(s"Got response from CWL: ${r.status} $body")
- // }
- // Await.result(x, 30.seconds)
- // r
- // }
+ Http()
+ .singleRequest(request, connectionContext = ConnectionContext.httpsClient(tls12Context))
.recoverWith {
case f =>
log.error(s"Failed to get CWL Live Feed: ${f.getMessage}")
@@ -221,8 +213,6 @@ object CWLFlight extends NodeSeqUnmarshaller {
val flightNodeSeq = xml \ "Body" \ "IATA_AIDX_FlightLegRS" \ "FlightLeg"
- println(s"Got ${flightNodeSeq.length} flights in CWL XML")
-
val flights = flightNodeSeq
.filter { n =>
(n \ "LegData" \ "AirportResources" \ "Resource").exists { p =>
@@ -322,7 +312,7 @@ object CWLFlight extends NodeSeqUnmarshaller {
maxPax = f.seatCapacity,
totalPax = f.paxCount,
transPax = None,
- terminal = Terminal(s"T${f.aircraftTerminal}"),
+ terminal = Terminal(f.aircraftTerminal),
voyageNumber = voyageNumber.numeric,
carrierCode = carrierCode.code,
flightCodeSuffix = suffix.map(_.suffix),
diff --git a/server/src/main/scala/uk/gov/homeoffice/drt/service/ProdFeedService.scala b/server/src/main/scala/uk/gov/homeoffice/drt/service/ProdFeedService.scala
index f719f35d0..3c67c52eb 100644
--- a/server/src/main/scala/uk/gov/homeoffice/drt/service/ProdFeedService.scala
+++ b/server/src/main/scala/uk/gov/homeoffice/drt/service/ProdFeedService.scala
@@ -24,6 +24,7 @@ import drt.server.feeds.bhx.{BHXClient, BHXFeed}
import drt.server.feeds.chroma.ChromaLiveFeed
import drt.server.feeds.cirium.CiriumFeed
import drt.server.feeds.common.{ManualUploadArrivalFeed, ProdHttpClient}
+import drt.server.feeds.cwl.{CWLClient, CWLFeed}
import drt.server.feeds.edi.EdiFeed
import drt.server.feeds.gla.GlaFeed
import drt.server.feeds.lcy.{LCYClient, LCYFeed}
@@ -429,6 +430,8 @@ case class ProdFeedService(journalType: StreamingJournalLike,
Feed(LGWFeed(azureClient)(system).source(Feed.actorRefSource), 5.seconds, 100.milliseconds)
case "BHX" if params.bhxIataEndPointUrl.nonEmpty =>
Feed(BHXFeed(BHXClient(params.bhxIataUsername, params.bhxIataEndPointUrl), Feed.actorRefSource), 5.seconds, 80.seconds)
+ case "CWL" if params.cwlIataEndPointUrl.nonEmpty =>
+ Feed(CWLFeed(CWLClient(params.cwlIataUsername, params.cwlIataEndPointUrl), Feed.actorRefSource), 5.seconds, 80.seconds)
case "LCY" if params.lcyLiveEndPointUrl.nonEmpty =>
Feed(LCYFeed(LCYClient(ProdHttpClient(), params.lcyLiveUsername, params.lcyLiveEndPointUrl, params.lcyLiveUsername, params.lcyLivePassword), Feed.actorRefSource), 5.seconds, 80.seconds)
case "LTN" =>
diff --git a/server/src/main/scala/uk/gov/homeoffice/drt/testsystem/TestTables.scala b/server/src/main/scala/uk/gov/homeoffice/drt/testsystem/TestTables.scala
index 93b480504..795fe532b 100644
--- a/server/src/main/scala/uk/gov/homeoffice/drt/testsystem/TestTables.scala
+++ b/server/src/main/scala/uk/gov/homeoffice/drt/testsystem/TestTables.scala
@@ -100,6 +100,8 @@ case class MockDrtParameters @Inject()() extends DrtParameters {
override val isSuperUserMode: Boolean = false
override val bhxIataEndPointUrl: String = ""
override val bhxIataUsername: String = ""
+ override val cwlIataEndPointUrl: String = ""
+ override val cwlIataUsername: String = ""
override val maybeBhxSoapEndPointUrl: Option[String] = None
override val maybeLtnLiveFeedUrl: Option[String] = None
override val maybeLtnLiveFeedUsername: Option[String] = None
diff --git a/server/src/test/scala/feeds/cwl/CWLFeedSpec.scala b/server/src/test/scala/feeds/cwl/CWLFeedSpec.scala
index 5c181c269..ac1a61308 100644
--- a/server/src/test/scala/feeds/cwl/CWLFeedSpec.scala
+++ b/server/src/test/scala/feeds/cwl/CWLFeedSpec.scala
@@ -8,13 +8,11 @@ import akka.http.scaladsl.unmarshalling.{Unmarshal, Unmarshaller}
import akka.stream.Materializer
import akka.stream.scaladsl.{Sink, Source}
import akka.testkit.TestProbe
-import drt.server.feeds.cwl.{CWLClient, CWLFlight, CWLFlightsResponse}
+import drt.server.feeds.cwl._
import drt.server.feeds.{ArrivalsFeedFailure, ArrivalsFeedResponse, ArrivalsFeedSuccess, Feed}
import services.crunch.CrunchTestLike
import uk.gov.homeoffice.drt.arrivals._
-import uk.gov.homeoffice.drt.ports.Terminals.T1
-import uk.gov.homeoffice.drt.ports.{LiveFeedSource, PortCode}
-import uk.gov.homeoffice.drt.time.SDate
+import uk.gov.homeoffice.drt.ports.Terminals.{T1, T2}
import scala.collection.immutable
import scala.concurrent.duration._
@@ -29,10 +27,9 @@ class CWLFeedSpec extends CrunchTestLike {
implicit val resToCWLResUM: Unmarshaller[HttpResponse, CWLFlightsResponse] = CWLFlight.responseToAUnmarshaller
"The CWL Feed client should successfully get a response from the CWL server" >> {
+ skipped("Exploratory test - requires VPN connection, correct feed url and username env vars")
-// skipped("Exploratory test - requires VPN connection, correct feed url and username env vars")
-
- val endpoint = "https://webserviceqa.cwl.aero/AIDXReadFlight/SERVICES/RequestFlightService.svc" //sys.env("CWL_IATA_ENDPOINT_URL")
+ val endpoint = sys.env("CWL_IATA_ENDPOINT_URL")
val username = sys.env("CWL_IATA_USERNAME")
val cwlClient = CWLClient(username, endpoint)
@@ -41,680 +38,538 @@ class CWLFeedSpec extends CrunchTestLike {
false
}
-// "Given some flight xml with 1 flight, I should get get back a list of 1 arrival" >> {
-//
-// val resp = HttpResponse(
-// entity = HttpEntity(
-// contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
-// cwlSoapResponse1FlightXml
-// )
-// )
-//
-// val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
-// .asInstanceOf[CWLFlightsResponseSuccess]
-// .flights
-// val expected = List(
-// CWLFlight(
-// "TOM",
-// "7623",
-// "PFO",
-// "CWL",
-// "1",
-// "ARR",
-// "2018-09-01T23:00:00.000Z",
-// arrival = true,
-// international = true,
-// None,
-// Option("2018-09-01T23:05:00.000Z"),
-// None,
-// Option("2018-09-01T23:00:00.000Z"),
-// Option("54L"),
-// Option("44"),
-// Option(189),
-// Option(65)
-// )
-// )
-//
-// result === expected
-// }
-//
-// val flight1: CWLFlight = CWLFlight(
-// "TOM",
-// "7623",
-// "PFO",
-// "CWL",
-// "1",
-// "ARR",
-// "2018-09-01T23:00:00.000Z",
-// arrival = true,
-// international = true,
-// None,
-// Option("2018-09-01T23:05:00.000Z"),
-// None,
-// Option("2018-09-01T23:00:00.000Z"),
-// Option("54L"),
-// Option("44"),
-// Option(189)
-// )
-//
-// val flight2: CWLFlight = CWLFlight(
-// "FR",
-// "8045",
-// "CHQ",
-// "CWL",
-// "2",
-// "ARR",
-// "2018-09-18T23:00:00.000Z",
-// arrival = true,
-// international = true,
-// None,
-// Option("2018-09-18T23:05:00.000Z"),
-// None,
-// Option("2018-09-18T23:00:00.000Z"),
-// Option("1"),
-// Option("1"),
-// Option(189)
-// )
-//
-// "Given some flight xml with 2 flights, I should get get back 2 arrival objects" >> {
-//
-// val resp = HttpResponse(
-// entity = HttpEntity(
-// contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
-// cwlSoapResponse2FlightsXml
-// )
-// )
-//
-// val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
-// .asInstanceOf[CWLFlightsResponseSuccess]
-// .flights
-// val expected = List(
-// flight1,
-// flight2
-// )
-//
-// result === expected
-// }
-//
-// "Given a flight with multiple types of passengers, those passenger numbers should be added together" >> {
-//
-// val resp = HttpResponse(
-// entity = HttpEntity(
-// contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
-// multiplePassengerTypesXML
-// )
-// )
-//
-// val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
-// .asInstanceOf[CWLFlightsResponseSuccess]
-// .flights
-// .head
-// .paxCount
-//
-// val expected = Option(71)
-//
-// result === expected
-// }
-//
-// case class CWLMockClient(xmlResponse: String, cwlLiveFeedUser: String = "", soapEndPoint: String = "") extends CWLClientLike {
-//
-//
-// def makeRequest(endpoint: String, headers: List[HttpHeader], postXML: String)
-// (implicit system: ActorSystem): Future[HttpResponse] = Future(HttpResponse(
-// entity = HttpEntity(
-// contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
-// xmlResponse
-// )))
-// }
-//
-// "Given a request for a full refresh of all flights, if it's successful the client should return all the flights" >> {
-// val client = CWLMockClient(cwlSoapResponse2FlightsXml)
-//
-// val result = Await
-// .result(client.initialFlights, 1.second).asInstanceOf[ArrivalsFeedSuccess].arrivals
-// val expected = List(
-// CWLFlight.cwlFlightToArrival(flight1),
-// CWLFlight.cwlFlightToArrival(flight2)
-// )
-//
-// result === expected
-// }
-//
-// "Given a request for a full refresh of all flights, if we are rate limited then we should get an ArrivalsFeedFailure" >> {
-// val client = CWLMockClient(rateLimitReachedResponse)
-//
-// val result = Await.result(client.initialFlights, 1.second)
-//
-// result must haveClass[ArrivalsFeedFailure]
-// }
-//
-// "Given a mock client returning an invalid XML response I should get an ArrivalFeedFailure " >> {
-// val client = CWLMockClient(invalidXmlResponse)
-//
-// val result = Await.result(client.initialFlights, 1.second)
-//
-// result must haveClass[ArrivalsFeedFailure]
-// }
-//
-// case class CWLMockClientWithUpdates(initialResponses: List[ArrivalsFeedResponse], updateResponses: List[ArrivalsFeedResponse]) extends CWLClientLike {
-//
-// var mockInitialResponse: immutable.Seq[ArrivalsFeedResponse] = initialResponses
-// var mockUpdateResponses: immutable.Seq[ArrivalsFeedResponse] = updateResponses
-//
-// override def initialFlights(implicit actorSystem: ActorSystem, materializer: Materializer): Future[ArrivalsFeedResponse] = mockInitialResponse match {
-// case head :: tail =>
-// mockInitialResponse = tail
-// Future(head)
-// case Nil =>
-// Future(ArrivalsFeedFailure("No more mock esponses"))
-// }
-//
-// override def updateFlights(implicit actorSystem: ActorSystem, materializer: Materializer): Future[ArrivalsFeedResponse] =
-// mockUpdateResponses match {
-// case head :: tail =>
-// mockUpdateResponses = tail
-// Future(head)
-//
-// case Nil =>
-// Future(ArrivalsFeedFailure("No more mock esponses"))
-// }
-//
-// def makeRequest(endpoint: String, headers: List[HttpHeader], postXML: String)
-// (implicit system: ActorSystem): Future[HttpResponse] = ???
-//
-// override val cwlLiveFeedUser: String = ""
-// override val soapEndPoint: String = ""
-// }
-//
-// "Given a request for a full refresh of all flights fails, we should poll for a full request until it succeeds" >> {
-// val firstFailure = ArrivalsFeedFailure("First Failure")
-// val secondFailure = ArrivalsFeedFailure("Second Failure")
-// val finallySuccess = ArrivalsFeedSuccess(List())
-//
-// val initialResponses = List(firstFailure, secondFailure, finallySuccess)
-// val updateResponses = List(finallySuccess)
-//
-// val feed = CWLFeed(
-// CWLMockClientWithUpdates(initialResponses, updateResponses),
-// Feed.actorRefSource
-// )
-//
-// val probe = TestProbe()
-// val expected = Seq(firstFailure, secondFailure, finallySuccess, finallySuccess)
-// val actorSource = feed.take(4).to(Sink.actorRef(probe.ref, NotUsed)).run()
-// Source(1 to 4).map(_ => actorSource ! Feed.Tick).run()
-//
-// probe.receiveN(4, 1.second) === expected
-// }
-//
-// "Given a successful initial request, followed by a failed update, we should continue to poll for updates" >> {
-//
-// val failure = ArrivalsFeedFailure("First Failure")
-// val finallySuccess = ArrivalsFeedSuccess(List())
-//
-// val initialResponses = List(finallySuccess)
-// val updateResponses = List(failure, finallySuccess)
-//
-// val feed = CWLFeed(
-// CWLMockClientWithUpdates(initialResponses, updateResponses),
-// Feed.actorRefSource
-// )
-//
-// val expected = Seq(finallySuccess, failure, finallySuccess)
-// val probe = TestProbe()
-// val actorSource = feed.take(3).to(Sink.actorRef(probe.ref, NotUsed)).run()
-// Source(1 to 3).map(_ => actorSource ! Feed.Tick).run()
-//
-// probe.receiveN(3, 1.second) === expected
-// }
-//
-// "Given a list of operation times I should be able to extract the scheduled time" >> {
-// val xml =
-// XML.loadString(
-// """
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:00:00.000Z
-// |
-// """.stripMargin)
-//
-// val expected = "2018-09-01T23:00:00.000Z"
-// val node = xml \ "OperationTime"
-// val result = CWLFlight.scheduledTime(node).get
-//
-// result === expected
-// }
-//
-// "Given a list of operation times I should be able to extract the actual chox time" >> {
-// val xml =
-// XML.loadString(
-// """
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T24:00:00.000Z
-// |
-// """.stripMargin)
-//
-//
-// val expected = "2018-09-01T24:00:00.000Z"
-// val node = xml \ "OperationTime"
-// val result = CWLFlight.actualChox(node).get
-//
-// result === expected
-// }
-//
-// "Given a list of operation times I should be able to extract the estimated chox time" >> {
-// val xml =
-// XML.loadString(
-// """
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T24:00:00.000Z
-// |
-// """.stripMargin)
-//
-// val expected = "2018-09-01T24:00:00.000Z"
-// val node = xml \ "OperationTime"
-// val result = CWLFlight.estChox(node).get
-//
-// result === expected
-// }
-//
-// "Given a list of operation times I should be able to extract the estimated touchdown time" >> {
-// val xml =
-// XML.loadString(
-// """
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T24:00:00.000Z
-// |
-// """.stripMargin)
-//
-// val expected = "2018-09-01T24:00:00.000Z"
-// val node = xml \ "OperationTime"
-// val result = CWLFlight.estTouchDown(node).get
-//
-// result === expected
-// }
-//
-// "Given a list of operation times I should be able to extract the actual touchdown time" >> {
-// val xml =
-// XML.loadString(
-// """
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T24:00:00.000Z
-// |
-// """.stripMargin)
-//
-//
-// val expected = "2018-09-01T24:00:00.000Z"
-// val node = xml \ "OperationTime"
-// val result = CWLFlight.actualTouchDown(node).get
-//
-// result === expected
-// }
-//
-// "Given a CWLFlight, I should get an Arrival back with the same fields - we should not use Est Chox" >> {
-// val estimatedOnBlocksTimeString = "2018-09-01T23:05:00.000Z"
-// val actualOnBlocksTimeString = "2018-09-01T23:06:00.000Z"
-// val estimatedTouchDownTimeString = "2018-09-01T23:07:00.000Z"
-// val actualTouchDownTimeString = "2018-09-01T23:08:00.000Z"
-// val scheduledTimeString = "2018-09-01T23:00:00.000Z"
-//
-// val cwlFlight = CWLFlight(
-// "SA",
-// "123",
-// "JNB",
-// "CWL",
-// "1",
-// "ARR",
-// scheduledTimeString,
-// arrival = true,
-// international = true,
-// Option(estimatedOnBlocksTimeString),
-// Option(actualOnBlocksTimeString),
-// Option(estimatedTouchDownTimeString),
-// Option(actualTouchDownTimeString),
-// Option("55"),
-// Option("6"),
-// Option(175),
-// Option(65),
-// Nil
-// )
-//
-// val result = CWLFlight.cwlFlightToArrival(cwlFlight)
-//
-// val expected = LiveArrival(
-// operator = Option("SA"),
-// maxPax = Option(175),
-// totalPax = Option(65),
-// transPax = None,
-// terminal = T1,
-// voyageNumber = 123,
-// carrierCode = "SA",
-// flightCodeSuffix = None,
-// origin = "JNB",
-// scheduled = SDate(scheduledTimeString).millisSinceEpoch,
-// estimated = Option(SDate(estimatedTouchDownTimeString).millisSinceEpoch),
-// touchdown = Option(SDate(actualTouchDownTimeString).millisSinceEpoch),
-// estimatedChox = None,
-// actualChox = Option(SDate(actualOnBlocksTimeString).millisSinceEpoch),
-// status = "ARR",
-// gate = Option("6"),
-// stand = Option("55"),
-// runway = None,
-// baggageReclaim = None,
-// )
-//
-// result === expected
-// }
-//
-// "Given a CWLFlight with 0 for passenger fields, I should see 0 pax, 0 max pax and 0 transfer pax." >> {
-// val client = CWLMockClient(cwlSoapResponseWith0PaxXml)
-//
-// val result = Await
-// .result(client.initialFlights, 1.second).asInstanceOf[ArrivalsFeedSuccess].arrivals
-//
-// val actMax = result match {
-// case f :: _ => (f.totalPax, f.maxPax)
-// }
-//
-// val expected = (Some(0), Some(0))
-//
-// actMax === expected
-// }
-//
-// def multiplePassengerTypesXML: String =
-// """
-// |
-// |
-// |
-// |
-// |
-// | SN
-// | 1234
-// | TST
-// | CWL
-// | 2019-08-05
-// |
-// |
-// |
-// |
-// |
-// |
-// |
-// | 68
-// | 1
-// | 1
-// | 1
-// | 88
-// |
-// | ARR
-// |
-// |
-// |
-// | 5
-// | 0
-// |
-// | 1
-// |
-// |
-// | 2018-09-01T23:00:00.000Z
-// |
-// |
-// |
-// |
-// |
-// |
-// |
-// |
-// |
-// """.stripMargin
-//
-// def cwlSoapResponse1FlightXml: String =
-// """
-// |
-// |
-// |
-// |
-// |
-// | TOM
-// | 7623
-// | PFO
-// | CWL
-// | 2018-09-01
-// |
-// |
-// |
-// |
-// | C
-// |
-// |
-// |
-// | 189
-// | 65
-// |
-// | ARR
-// |
-// |
-// |
-// | 54L
-// | 44
-// |
-// | 1
-// | 3
-// |
-// |
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:05:00.000Z
-// |
-// | 73H
-// |
-// |
-// |
-// | S
-// |
-// |
-// |
-// |
-// |
-// | Thomson Airways
-// | Paphos
-// | Birmingham
-// |
-// |
-// |
-// |
-// |
-// """.stripMargin
-//
-// def cwlSoapResponseWith0PaxXml: String =
-// """
-// |
-// |
-// |
-// |
-// |
-// | TOM
-// | 7623
-// | PFO
-// | CWL
-// | 2018-09-01
-// |
-// |
-// |
-// |
-// | C
-// |
-// |
-// |
-// | 0
-// | 0
-// |
-// | ARR
-// |
-// |
-// |
-// | 54L
-// | 44
-// |
-// | 1
-// | 3
-// |
-// |
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:05:00.000Z
-// |
-// | 73H
-// |
-// |
-// |
-// | S
-// |
-// |
-// |
-// |
-// |
-// | Thomson Airways
-// | Paphos
-// | Birmingham
-// |
-// |
-// |
-// |
-// |
-// """.stripMargin
-//
-// def cwlSoapResponse2FlightsXml: String =
-// """
-// |
-// |
-// |
-// |
-// |
-// | TOM
-// | 7623
-// | PFO
-// | CWL
-// | 2018-09-01
-// |
-// |
-// |
-// |
-// | C
-// |
-// |
-// |
-// | 189
-// |
-// | ARR
-// |
-// |
-// |
-// | 54L
-// | 44
-// |
-// | 1
-// | 3
-// |
-// |
-// |
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:00:00.000Z
-// | 2018-09-01T23:05:00.000Z
-// |
-// | 73H
-// |
-// |
-// |
-// | S
-// |
-// |
-// |
-// |
-// |
-// | Thomson Airways
-// | Paphos
-// | Birmingham
-// |
-// |
-// |
-// |
-// | FR
-// | 8045
-// | CHQ
-// | CWL
-// | 2018-09-18
-// |
-// |
-// |
-// |
-// | J
-// |
-// |
-// |
-// | 180
-// |
-// |
-// | 9
-// |
-// | ARR
-// |
-// |
-// |
-// | 1
-// | 1
-// |
-// | 2
-// | 7
-// |
-// |
-// |
-// | 2018-09-18T23:00:00.000Z
-// | 2018-09-18T23:00:00.000Z
-// | 2018-09-18T23:05:00.000Z
-// |
-// | 73H
-// |
-// |
-// |
-// | S
-// |
-// |
-// |
-// |
-// |
-// | Ryanair
-// | Chania (s)
-// | Birmingham
-// |
-// |
-// |
-// |
-// |
-// """.stripMargin
-//
-// def rateLimitReachedResponse: String =
-// """
-// |
-// |
-// |
-// |
-// |
-// | Warning: Full Refresh not possible at this time please try in 590 seconds.
-// |
-// |
-// |
-// |
-// """.stripMargin
-//
-// def invalidXmlResponse: String =
-// """
-// |Blah blah
-// """.stripMargin
+ "Given some flight xml with 1 flight with multiple passenger types, I should get 1 arrival with passengers summed" >> {
+
+ val resp = HttpResponse(
+ entity = HttpEntity(
+ contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
+ multiplePassengerTypesXML
+ )
+ )
+
+ val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
+ .asInstanceOf[CWLFlightsResponseSuccess]
+ .flights
+ val expected = List(
+ CWLFlight(
+ airline = "AA",
+ flightNumber = "1234",
+ departureAirport = "CDG",
+ arrivalAirport = "CWL",
+ aircraftTerminal = "T1",
+ status = "LBG",
+ scheduledOnBlocks = "2024-12-23T09:45:00.000Z",
+ arrival = true,
+ international = true,
+ estimatedOnBlocks = Some("2024-12-23T09:55:00.000Z"),
+ actualOnBlocks = Option("2024-12-23T09:59:00.000Z"),
+ estimatedTouchDown = None,
+ actualTouchDown = Option("2024-12-23T09:52:54.000Z"),
+ aircraftParkingPosition = Option("10"),
+ passengerGate = Option("10"),
+ seatCapacity = Option(88),
+ paxCount = Option(55)
+ )
+ )
+
+ result === expected
+ }
+
+ "Given some flight xml with 2 flights, I should get get back 2 arrival objects" >> {
+
+ val resp = HttpResponse(
+ entity = HttpEntity(
+ contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
+ cwlSoapResponse2FlightsXml
+ )
+ )
+
+ val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
+ .asInstanceOf[CWLFlightsResponseSuccess]
+ .flights.size
+
+ result === 2
+ }
+
+ "Given a flight with multiple types of passengers, those passenger numbers should be added together" >> {
+ val resp = HttpResponse(
+ entity = HttpEntity(
+ contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
+ multiplePassengerTypesXML
+ )
+ )
+
+ val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
+ .asInstanceOf[CWLFlightsResponseSuccess]
+ .flights
+ .head
+ .paxCount
+
+ result === Option(55)
+ }
+
+ "Given only a departure flight no flights should be returned" >> {
+ val resp = HttpResponse(
+ entity = HttpEntity(
+ contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
+ departureFlightXML
+ )
+ )
+
+ val result = Await.result(Unmarshal[HttpResponse](resp).to[CWLFlightsResponse], 5.seconds)
+ .asInstanceOf[CWLFlightsResponseSuccess]
+ .flights.size
+
+ result === 0
+ }
+
+ case class CWLMockClient(xmlResponse: String, cwlLiveFeedUser: String = "", soapEndPoint: String = "") extends CWLClientLike {
+
+
+ def makeRequest(endpoint: String, headers: List[HttpHeader], postXML: String)
+ (implicit system: ActorSystem): Future[HttpResponse] = Future(HttpResponse(
+ entity = HttpEntity(
+ contentType = ContentType(MediaTypes.`application/xml`, HttpCharsets.`UTF-8`),
+ xmlResponse
+ )))
+ }
+
+ "Given a request for a full refresh of all flights, if it's successful the client should return all the flights" >> {
+ val client = CWLMockClient(cwlSoapResponse2FlightsXml)
+
+ val result = Await
+ .result(client.initialFlights, 1.second).asInstanceOf[ArrivalsFeedSuccess].arrivals
+
+ result === List(
+ LiveArrival(Some("TOM"), Some(189), None, None, T1, 7623, "TOM", None, "PFO", 1535842800000L, None, Some(1535842800000L), None, Some(1535843100000L), "ARR", Some("44"), Some("54L"), None, None),
+ LiveArrival(Some("FR"), Some(189), None, None, T2, 8045, "FR", None, "CHQ", 1537311600000L, None, Some(1537311600000L), None, Some(1537311900000L), "ARR", Some("1"), Some("1"), None, None)
+ )
+ }
+
+
+ "Given a request for a full refresh of all flights, if we are rate limited then we should get an ArrivalsFeedFailure" >> {
+ val client = CWLMockClient(rateLimitReachedResponse)
+
+ val result = Await.result(client.initialFlights, 1.second)
+
+ result must haveClass[ArrivalsFeedFailure]
+ }
+
+ "Given a mock client returning an invalid XML response I should get an ArrivalFeedFailure " >> {
+ val client = CWLMockClient(invalidXmlResponse)
+
+ val result = Await.result(client.initialFlights, 1.second)
+
+ result must haveClass[ArrivalsFeedFailure]
+ }
+
+ case class CWLMockClientWithUpdates(initialResponses: List[ArrivalsFeedResponse], updateResponses: List[ArrivalsFeedResponse]) extends CWLClientLike {
+
+ var mockInitialResponse: immutable.Seq[ArrivalsFeedResponse] = initialResponses
+ var mockUpdateResponses: immutable.Seq[ArrivalsFeedResponse] = updateResponses
+
+ override def initialFlights(implicit actorSystem: ActorSystem, materializer: Materializer): Future[ArrivalsFeedResponse] = mockInitialResponse match {
+ case head :: tail =>
+ mockInitialResponse = tail
+ Future(head)
+ case Nil =>
+ Future(ArrivalsFeedFailure("No more mock esponses"))
+ }
+
+ override def updateFlights(implicit actorSystem: ActorSystem, materializer: Materializer): Future[ArrivalsFeedResponse] =
+ mockUpdateResponses match {
+ case head :: tail =>
+ mockUpdateResponses = tail
+ Future(head)
+
+ case Nil =>
+ Future(ArrivalsFeedFailure("No more mock esponses"))
+ }
+
+ def makeRequest(endpoint: String, headers: List[HttpHeader], postXML: String)
+ (implicit system: ActorSystem): Future[HttpResponse] = ???
+
+ override val cwlLiveFeedUser: String = ""
+ override val soapEndPoint: String = ""
+ }
+
+ "Given a request for a full refresh of all flights fails, we should poll for a full request until it succeeds" >> {
+ val firstFailure = ArrivalsFeedFailure("First Failure")
+ val secondFailure = ArrivalsFeedFailure("Second Failure")
+ val finallySuccess = ArrivalsFeedSuccess(List())
+
+ val initialResponses = List(firstFailure, secondFailure, finallySuccess)
+ val updateResponses = List(finallySuccess)
+
+ val feed = CWLFeed(
+ CWLMockClientWithUpdates(initialResponses, updateResponses),
+ Feed.actorRefSource
+ )
+
+ val probe = TestProbe()
+ val expected = Seq(firstFailure, secondFailure, finallySuccess, finallySuccess)
+ val actorSource = feed.take(4).to(Sink.actorRef(probe.ref, NotUsed)).run()
+ Source(1 to 4).map(_ => actorSource ! Feed.Tick).run()
+
+ probe.receiveN(4, 1.second) === expected
+ }
+
+ "Given a successful initial request, followed by a failed update, we should continue to poll for updates" >> {
+
+ val failure = ArrivalsFeedFailure("First Failure")
+ val finallySuccess = ArrivalsFeedSuccess(List())
+
+ val initialResponses = List(finallySuccess)
+ val updateResponses = List(failure, finallySuccess)
+
+ val feed = CWLFeed(
+ CWLMockClientWithUpdates(initialResponses, updateResponses),
+ Feed.actorRefSource
+ )
+
+ val expected = Seq(finallySuccess, failure, finallySuccess)
+ val probe = TestProbe()
+ val actorSource = feed.take(3).to(Sink.actorRef(probe.ref, NotUsed)).run()
+ Source(1 to 3).map(_ => actorSource ! Feed.Tick).run()
+
+ probe.receiveN(3, 1.second) === expected
+ }
+
+ "Given a list of operation times I should be able to extract the scheduled time" >> {
+ val xml =
+ XML.loadString(
+ """
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T23:00:00.000Z
+ |
+ """.stripMargin)
+
+ val expected = "2018-09-01T23:00:00.000Z"
+ val node = xml \ "OperationTime"
+ val result = CWLFlight.scheduledTime(node).get
+
+ result === expected
+ }
+
+ "Given a list of operation times I should be able to extract the actual chox time" >> {
+ val xml =
+ XML.loadString(
+ """
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T24:00:00.000Z
+ |
+ """.stripMargin)
+
+
+ val expected = "2018-09-01T24:00:00.000Z"
+ val node = xml \ "OperationTime"
+ val result = CWLFlight.actualChox(node).get
+
+ result === expected
+ }
+
+ "Given a list of operation times I should be able to extract the estimated chox time" >> {
+ val xml =
+ XML.loadString(
+ """
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T24:00:00.000Z
+ |
+ """.stripMargin)
+
+ val expected = "2018-09-01T24:00:00.000Z"
+ val node = xml \ "OperationTime"
+ val result = CWLFlight.estChox(node).get
+
+ result === expected
+ }
+
+ "Given a list of operation times I should be able to extract the estimated touchdown time" >> {
+ val xml =
+ XML.loadString(
+ """
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T24:00:00.000Z
+ |
+ """.stripMargin)
+
+ val expected = "2018-09-01T24:00:00.000Z"
+ val node = xml \ "OperationTime"
+ val result = CWLFlight.estTouchDown(node).get
+
+ result === expected
+ }
+
+ "Given a list of operation times I should be able to extract the actual touchdown time" >> {
+ val xml =
+ XML.loadString(
+ """
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T24:00:00.000Z
+ |
+ """.stripMargin)
+
+
+ val expected = "2018-09-01T24:00:00.000Z"
+ val node = xml \ "OperationTime"
+ val result = CWLFlight.actualTouchDown(node).get
+
+ result === expected
+ }
+
+ def multiplePassengerTypesXML: String =
+ """
+ |
+ |
+ |
+ |
+ |
+ | AA
+ | 1234
+ | CDG
+ | CWL
+ | 2024-12-23
+ |
+ |
+ |
+ |
+ | J
+ |
+ |
+ |
+ | 50
+ | 5
+ | 88
+ |
+ |
+ | DL
+ | 1111
+ |
+ |
+ | EY
+ | 2222
+ |
+ |
+ | KQ
+ | 3333
+ |
+ |
+ | VS
+ | 4444
+ |
+ | LBG
+ |
+ |
+ |
+ | 10
+ | 10
+ | 30
+ | T1
+ | A
+ |
+ |
+ | 2024-12-23T09:45:00.000Z
+ | 2024-12-23T09:55:00.000Z
+ | 2024-12-23T09:52:54.000Z
+ | 2024-12-23T09:59:00.000Z
+ |
+ | E90
+ |
+ | PHEZY
+ |
+ | SER
+ |
+ | KLM69Y
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ """.stripMargin
+
+ def departureFlightXML: String =
+ """
+ |
+ |
+ |
+ |
+ |
+ | RUK
+ | 9431
+ | CWL
+ | BFS
+ | 2023-07-02
+ |
+ |
+ |
+ |
+ | J
+ |
+ |
+ |
+ |
+ |
+ | DEP
+ |
+ |
+ |
+ | 11
+ | 30
+ |
+ |
+ |
+ | 2023-07-02T13:02:00.000Z
+ | 2023-07-02T13:02:00.000Z
+ | 2023-07-02T12:57:00.000Z
+ |
+ | 73H
+ |
+ | GRUKK
+ |
+ | NON
+ |
+ | RUK9431
+ |
+ |
+ |
+ |
+ |
+ |
+ |
+ """.stripMargin
+
+ def cwlSoapResponse2FlightsXml: String =
+ """
+ |
+ |
+ |
+ |
+ |
+ | TOM
+ | 7623
+ | PFO
+ | CWL
+ | 2018-09-01
+ |
+ |
+ |
+ |
+ | C
+ |
+ |
+ |
+ | 189
+ |
+ | ARR
+ |
+ |
+ |
+ | 54L
+ | 44
+ |
+ | T1
+ | 3
+ |
+ |
+ |
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T23:00:00.000Z
+ | 2018-09-01T23:05:00.000Z
+ |
+ | 73H
+ |
+ |
+ |
+ | S
+ |
+ |
+ |
+ |
+ |
+ | Thomson Airways
+ | Paphos
+ | Birmingham
+ |
+ |
+ |
+ |
+ | FR
+ | 8045
+ | CHQ
+ | CWL
+ | 2018-09-18
+ |
+ |
+ |
+ |
+ | J
+ |
+ |
+ |
+ | 180
+ |
+ |
+ | 9
+ |
+ | ARR
+ |
+ |
+ |
+ | 1
+ | 1
+ |
+ | T2
+ | 7
+ |
+ |
+ |
+ | 2018-09-18T23:00:00.000Z
+ | 2018-09-18T23:00:00.000Z
+ | 2018-09-18T23:05:00.000Z
+ |
+ | 73H
+ |
+ |
+ |
+ | S
+ |
+ |
+ |
+ |
+ |
+ | Ryanair
+ | Chania (s)
+ | Birmingham
+ |
+ |
+ |
+ |
+ |
+ """.stripMargin
+
+ def rateLimitReachedResponse: String =
+ """
+ |
+ |
+ |
+ |
+ |
+ | Warning: Full Refresh not possible at this time please try in 590 seconds.
+ |
+ |
+ |
+ |
+ """.stripMargin
+
+ def invalidXmlResponse: String =
+ """
+ |Blah blah
+ """.stripMargin
}