diff --git a/commons/util/src/main/scala/net/shrine/problem/DashboardProblemDatabase.scala b/commons/util/src/main/scala/net/shrine/problem/DashboardProblemDatabase.scala
index 347a53f52..540a02c66 100644
--- a/commons/util/src/main/scala/net/shrine/problem/DashboardProblemDatabase.scala
+++ b/commons/util/src/main/scala/net/shrine/problem/DashboardProblemDatabase.scala
@@ -1,209 +1,193 @@
package net.shrine.problem
import java.util.concurrent.TimeoutException
import javax.sql.DataSource
import com.typesafe.config.Config
import net.shrine.slick.{CouldNotRunDbIoActionException, TestableDataSourceCreator}
import slick.dbio.SuccessAction
import slick.driver.JdbcProfile
import slick.jdbc.meta.MTable
import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import scala.util.control.NonFatal
import scala.xml.XML
/**
* Problems database object, defines the PROBLEMS table schema and related queries,
* as well as all interactions with the database.
* @author ty
* @since 07/16
*/
object Problems {
val config:Config = ProblemConfigSource.config.getConfig("shrine.dashboard.database")
val slickProfileClassName = config.getString("slickProfileClassName")
// TODO: Can we not pay this 2 second cost here?
val slickProfile:JdbcProfile = ProblemConfigSource.objectForName(slickProfileClassName)
import slickProfile.api._
val dataSource: DataSource = TestableDataSourceCreator.dataSource(config)
lazy val db = {
val db = Database.forDataSource(dataSource)
val createTables: String = "createTablesOnStart"
if (config.hasPath(createTables) && config.getBoolean(createTables)) {
val duration = FiniteDuration(3, SECONDS)
Await.ready(db.run(IOActions.createIfNotExists), duration)
val testValues: String = "createTestValuesOnStart"
if (config.hasPath(testValues) && config.getBoolean(testValues)) {
def dumb(id: Int) = ProblemDigest(s"codec($id)", s"stamp($id)", s"sum($id)", s"desc($id)", {id} , id)
val dummyValues: Seq[ProblemDigest] = Seq(0, 1, 2, 3, 4, 5, 6).map(dumb)
Await.ready(db.run(Queries ++= dummyValues), duration)
}
}
db
}
/**
* The Problems Table. This is the table schema.
*/
class ProblemsT(tag: Tag) extends Table[ProblemDigest](tag, Queries.tableName) {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def codec = column[String]("codec")
def stampText = column[String]("stampText")
def summary = column[String]("summary")
def description = column[String]("description")
def xml = column[String]("detailsXml", O.SqlType("Clob"))
def epoch= column[Long]("epoch")
// projection between table row and problem digest
def * = (id, codec, stampText, summary, description, xml, epoch) <> (rowToProblem, problemToRow)
def idx = index("idx_epoch", epoch, unique=false)
/**
* Converts a table row into a ProblemDigest.
* @param args the table row, represented as a five-tuple string
* @return the corresponding ProblemDigest
*/
def rowToProblem(args: (Int, String, String, String, String, String, Long)): ProblemDigest = args match {
case (id, codec, stampText, summary, description, detailsXml, epoch) =>
ProblemDigest(codec, stampText, summary, description, XML.loadString(detailsXml), epoch)
}
/**
* Converts a ProblemDigest into an Option of a table row. For now there is no failure
* condition, ie a ProblemDigest can always be a table row, but this is a place for
* possible future error handling
* @param problem the ProblemDigest to convert
* @return an Option of a table row.
*/
def problemToRow(problem: ProblemDigest): Option[(Int, String, String, String, String, String, Long)] = problem match {
case ProblemDigest(codec, stampText, summary, description, detailsXml, epoch) =>
// 7 is ignored on insert and replaced with an auto incremented id
Some((7, codec, stampText, summary, description, detailsXml.toString, epoch))
}
}
/**
* Queries related to the Problems table.
*/
object Queries extends TableQuery(new ProblemsT(_)) {
/**
* The table name
*/
val tableName = "problems"
/**
* Equivalent to Select * from Problems;
*/
val selectAll = this
/**
* Selects all the details xml sorted by the problem's time stamp.
*/
val selectDetails = this.map(_.xml)
/**
* Sorts the problems in descending order
*/
val descending = this.sortBy(_.epoch.desc)
/**
* Selects the last N problems, after the offset
*/
def lastNProblems(n: Int, offset: Int = 0) = this.descending.drop(offset).take(n)
}
/**
* DBIO Actions. These are pre-defined IO actions that may be useful.
* Using it to centralize the location of DBIOs.
*/
object IOActions {
val problems = Queries
val tableExists = MTable.getTables(problems.tableName).map(_.nonEmpty)
val createIfNotExists = tableExists.flatMap(
if (_) SuccessAction(NoOperation) else problems.schema.create)
val dropIfExists = tableExists.flatMap(
if (_) problems.schema.drop else SuccessAction(NoOperation))
val resetTable = createIfNotExists >> problems.selectAll.delete
val selectAll = problems.result
def sizeAndProblemDigest(n: Int, offset: Int = 0) = problems.lastNProblems(n, offset).result.zip(problems.size.result)
def findIndexOfDate(date: Long) = (problems.size - problems.filter(_.epoch <= date).size).result
- def ++=(sequence: Seq[ProblemDigest]) = problems ++= sequence
}
/**
* Entry point for interacting with the database. Runs IO actions.
*/
object DatabaseConnector {
val IO = IOActions
/**
* Executes a series of IO actions as a single transactions
*/
def executeTransaction(actions: DBIOAction[_, NoStream, _]*): Future[Unit] = {
db.run(DBIO.seq(actions:_*).transactionally)
}
/**
* Executes a series of IO actions as a single transaction, synchronous
*/
def executeTransactionBlocking(actions: DBIOAction[_, NoStream, _]*)(implicit timeout: Duration): Unit = {
try {
Await.ready(this.executeTransaction(actions: _*), timeout)
} catch {
// TODO: Handle this better
case tx:TimeoutException => throw CouldNotRunDbIoActionException(Problems.dataSource, tx)
case NonFatal(x) => throw CouldNotRunDbIoActionException(Problems.dataSource, x)
}
}
/**
* Straight copy of db.run
*/
def run[R](dbio: DBIOAction[R, NoStream, _]): Future[R] = {
db.run(dbio)
}
/**
* Synchronized copy of db.run
*/
def runBlocking[R](dbio: DBIOAction[R, NoStream, _])(implicit timeout: Duration = new FiniteDuration(15, SECONDS)): R = {
try {
Await.result(this.run(dbio), timeout)
} catch {
case tx:TimeoutException => throw CouldNotRunDbIoActionException(Problems.dataSource, tx)
case NonFatal(x) => throw CouldNotRunDbIoActionException(Problems.dataSource, x)
}
}
- def insertProblem(problem: ProblemDigest)(implicit timeout: Duration = new FiniteDuration(15, SECONDS)) = {
+ def insertProblem(problem: ProblemDigest, timeout: Duration = new FiniteDuration(15, SECONDS)) = {
runBlocking(Queries += problem)(timeout)
}
}
}
// For SuccessAction, just a no_op.
-case object NoOperation
-
-object ProblemConsumer {
- val problems:mutable.Stack[ProblemDigest] = mutable.Stack()
-
- def put(p:ProblemDigest) {
- synchronized(problems.push(p))
- }
-
- def batchInsert() = {
- Problems.DatabaseConnector.runBlocking(Problems.IOActions ++= problems.elems)
- problems.clear()
- }
-
-
-}
\ No newline at end of file
+case object NoOperation
\ No newline at end of file
diff --git a/commons/util/src/main/scala/net/shrine/problem/Problem.scala b/commons/util/src/main/scala/net/shrine/problem/Problem.scala
index 673a8a6ba..b9ac30111 100644
--- a/commons/util/src/main/scala/net/shrine/problem/Problem.scala
+++ b/commons/util/src/main/scala/net/shrine/problem/Problem.scala
@@ -1,258 +1,225 @@
package net.shrine.problem
import java.net.InetAddress
-import java.text.SimpleDateFormat
import java.util.Date
import java.util.concurrent.Executors
import net.shrine.log.Loggable
import net.shrine.serialization.{XmlMarshaller, XmlUnmarshaller}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.xml.{Elem, Node, NodeSeq}
/**
* Describes what information we have about a problem at the site in code where we discover it.
*
* @author david
* @since 8/6/15
*/
trait Problem {
def summary:String
def problemName = getClass.getName
def throwable:Option[Throwable] = None
def stamp:Stamp
def description:String
def exceptionXml(exception:Option[Throwable]): Option[Elem] = {
exception.map{x =>
{x.getClass.getName}
{x.getMessage}
{x.getStackTrace.map(line => {line})}{exceptionXml(Option(x.getCause)).getOrElse("")}
}}
def throwableDetail: Option[Elem] = exceptionXml(throwable)
def detailsXml: NodeSeq = NodeSeq.fromSeq({throwableDetail.getOrElse("")} )
- def toDigest:ProblemDigest = ProblemDigest(problemName,stamp.pretty,summary,description,detailsXml, stamp.time)
-
- def createAndLog:Problem = {
- if (!ProblemConfigSource.turnOffConnector)
- Problems.DatabaseConnector.insertProblem(toDigest)
- this
- }
+ def toDigest:ProblemDigest = ProblemDigest(problemName,stamp.pretty,summary,description,detailsXml,stamp.time)
/**
- * The hack that will get us through until onCreate in 2.13
- * The problem is that we want to insert the createAndLog call after a problem is constructed.
- * The only way to currently do that is with DelayedInit... which is just no.
- * Thus, the hack (that's still better than DelayedInit) is to watch the summary, description,
- * and throwable field, and call createAndLog once we know they've been initialized. The one
- * caveat is that creating throwable is optional, so in the worst case we wait 25 ms then decide
- * it's not gettting initialized.
- * @return
+ * Temporary replacement for onCreate, which will be released come Scala 2.13
+ * TODO: remove when Scala 2.13 releases
*/
- def logAfterInitialization:Future[Problem] = {
- import MyExecutionContext.ioThreadPool
+ def hackToHandleAfterInitialization(handler:ProblemHandler):Future[Unit] = {
+ import ProblemExecutionContext.ioThreadPool
Future {
var continue = true
- while (continue) {
- Thread.sleep(5)
- try {
- continue = synchronized(summary) == null || synchronized(description) == null
- } catch {
- case a:UninitializedFieldError => continue = true
- }
- }
- var count = 0
- while (count < 5 && synchronized(throwable).isEmpty) {
- Thread.sleep(5)
- count += 1
- }
- continue = true
- var p: Option[Problem] = None
while (continue) {
try {
- p = Some(createAndLog)
+ handler.handleProblem(this)
continue = false
} catch {
- case a:UninitializedFieldError =>
+ case un:UninitializedFieldError =>
Thread.sleep(5)
continue = true
}
}
- p.get
+ Unit
}
}
}
+
case class ProblemDigest(codec: String, stampText: String, summary: String, description: String, detailsXml: NodeSeq, epoch: Long) extends XmlMarshaller {
override def toXml: Node = {
{codec}
{stampText}
{summary}
{description}
{epoch}
{detailsXml}
}
/**
* Ignores detailXml. equals with scala.xml is impossible. See http://www.scala-lang.org/api/2.10.3/index.html#scala.xml.Equality$
*/
override def equals(other: Any): Boolean =
other match {
case that: ProblemDigest =>
(that canEqual this) &&
codec == that.codec &&
stampText == that.stampText &&
summary == that.summary &&
description == that.description &&
epoch == that.epoch
case _ => false
}
/**
* Ignores detailXml
*/
override def hashCode: Int = {
val prime = 67
codec.hashCode + prime * (stampText.hashCode + prime *(summary.hashCode + prime * (description.hashCode + prime * epoch.hashCode())))
}
}
object ProblemDigest extends XmlUnmarshaller[ProblemDigest] with Loggable {
override def fromXml(xml: NodeSeq): ProblemDigest = {
val problemNode = xml \ "problem"
require(problemNode.nonEmpty,s"No problem tag in $xml")
def extractText(tagName:String) = (problemNode \ tagName).text
val codec = extractText("codec")
val stampText = extractText("stamp")
val summary = extractText("summary")
val description = extractText("description")
val detailsXml: NodeSeq = problemNode \ "details"
val epoch =
try { extractText("epoch").toLong }
catch { case nx:NumberFormatException =>
error(s"While parsing xml representing a ProblemDigest, the epoch could not be parsed into a long", nx)
0
}
ProblemDigest(codec,stampText,summary,description,detailsXml,epoch)
}
}
case class Stamp(host:InetAddress,time:Long,source:ProblemSources.ProblemSource) {
def pretty = s"${new Date(time)} on ${host.getHostName} ${source.pretty}"
}
object Stamp {
//TODO: val dateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")?
//TODO: Currently the stamp text is locale specific, which can change depending on the jre/computer running it...
- def apply(source:ProblemSources.ProblemSource, timer: => Long): Stamp = Stamp(InetAddress.getLocalHost, timer,source)
+ def apply(source:ProblemSources.ProblemSource, timer: => Long): Stamp = Stamp(InetAddress.getLocalHost, timer, source)
}
-/**
- * An abstract problem to enable easy creation of Problems. Note that when overriding fields,
- * you should only use def or lazy val, and not val.
- * See: http://stackoverflow.com/questions/15346600/field-inside-object-which-extends-app-trait-is-set-to-null-why-is-that-so
- * @param source
- */
abstract class AbstractProblem(source:ProblemSources.ProblemSource) extends Problem {
def timer = System.currentTimeMillis
override val stamp = Stamp(source, timer)
- logAfterInitialization
+ hackToHandleAfterInitialization(DatabaseProblemHandler)
}
trait ProblemHandler {
def handleProblem(problem:Problem)
}
/**
* An example problem handler
*/
object LoggingProblemHandler extends ProblemHandler with Loggable {
override def handleProblem(problem: Problem): Unit = {
problem.throwable.fold(error(problem.toString))(throwable =>
error(problem.toString,throwable)
)
}
}
-//object DatabaseProblemhandler extends ProblemHandler {
-// override def handleProblem(problem: Problem): Unit = {
-// Problems.DatabaseConnector.insertProblem(problem.toDigest)
-// }
-//
-//}
+object DatabaseProblemHandler extends ProblemHandler {
+ override def handleProblem(problem: Problem): Unit = {
+ if (!ProblemConfigSource.turnOffConnector)
+ Problems.DatabaseConnector.insertProblem(problem.toDigest)
+ }
+}
object ProblemSources{
sealed trait ProblemSource {
def pretty = getClass.getSimpleName.dropRight(1)
}
case object Adapter extends ProblemSource
case object Hub extends ProblemSource
case object Qep extends ProblemSource
case object Dsa extends ProblemSource
case object Unknown extends ProblemSource
def problemSources = Set(Adapter,Hub,Qep,Dsa,Unknown)
}
case class ProblemNotYetEncoded(internalSummary:String,t:Option[Throwable] = None) extends AbstractProblem(ProblemSources.Unknown){
override val summary = "An unanticipated problem encountered."
override val throwable = {
val rx = t.fold(new IllegalStateException(s"$summary"))(
new IllegalStateException(s"$summary",_)
)
rx.fillInStackTrace()
Some(rx)
}
val reportedAtStackTrace = new IllegalStateException("Capture reporting stack trace.")
override val description = "This problem is not yet classified in Shrine source code. Please report the details to the Shrine dev team."
override val detailsXml: NodeSeq = NodeSeq.fromSeq(
{internalSummary}
{throwableDetail.getOrElse("")}
)
}
object ProblemNotYetEncoded {
def apply(summary:String,x:Throwable):ProblemNotYetEncoded = ProblemNotYetEncoded(summary,Some(x))
}
-object MyExecutionContext {
+object ProblemExecutionContext {
private val processes = Runtime.getRuntime.availableProcessors()
private val factor = 3
private val threads = processes * factor
implicit val ioThreadPool: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(threads))
}
\ No newline at end of file
diff --git a/commons/util/src/test/scala/net/shrine/problem/DashboardProblemDatabaseTest.scala b/commons/util/src/test/scala/net/shrine/problem/DashboardProblemDatabaseTest.scala
index df03a47f1..ead103d82 100644
--- a/commons/util/src/test/scala/net/shrine/problem/DashboardProblemDatabaseTest.scala
+++ b/commons/util/src/test/scala/net/shrine/problem/DashboardProblemDatabaseTest.scala
@@ -1,92 +1,92 @@
package net.shrine.problem
import org.scalatest.concurrent.ScalaFutures
import org.scalatest.{BeforeAndAfter, FlatSpec, Matchers}
import slick.driver.H2Driver.api._
import scala.concurrent.duration._
/**
* Test creation, insertion, querying, and deletion of ProblemDigest values into an
* in-memory H2 database. Demonstrates proof of concept mapping of ProblemDigest
* case class into a database.
*/
class DashboardProblemDatabaseTest extends FlatSpec with BeforeAndAfter with ScalaFutures with Matchers {
implicit val timeout = 10.seconds
val connector = Problems.DatabaseConnector
val IO = connector.IO
val problemDigests = Seq(
ProblemDigest("MJPG", "01:01:01", "summary here", "description here" , uh not sure , 2),
ProblemDigest("wewu", "01:02:01", "coffee spill", "coffee everywhere" , He chose decaf , 1),
ProblemDigest("wuwu", "02:01:01", "squirrel" , "chewed all the cables", Like ALL of them , 0),
ProblemDigest("code", "10:01:02", "such summary", "such description" , Wow , 3))
before {
connector.runBlocking(IO.dropIfExists >> IO.tableExists) shouldBe false
connector.runBlocking(IO.createIfNotExists >> IO.tableExists) shouldBe true
connector.runBlocking(IO.createIfNotExists) shouldBe NoOperation
connector.runBlocking(IO.selectAll) shouldBe empty
}
after {
connector.runBlocking(IO.tableExists) shouldBe true
connector.runBlocking(IO.dropIfExists >> IO.tableExists) shouldBe false
connector.runBlocking(IO.dropIfExists) shouldBe NoOperation
}
"The Database" should "Connect without any problems" in {
// Insert the suppliers and ProblemDigests
connector.executeTransactionBlocking(IO.problems ++= problemDigests)
// Test that they are all in the table
var * = connector.runBlocking(IO.selectAll)
* should contain theSameElementsAs problemDigests
* should have length problemDigests.length
// Reset the table
connector.runBlocking(IO.resetTable >> IO.selectAll) shouldBe empty
// Run the test again
connector.executeTransactionBlocking(IO.problems += problemDigests.head,
IO.problems += problemDigests(1),
IO.problems += problemDigests(2),
IO.problems += problemDigests(3))
// Test that they are all in the table
* = connector.runBlocking(IO.selectAll)
* should contain theSameElementsAs problemDigests
* should have length problemDigests.length
// Test that the simple select and filter queries work
val filtered = connector.runBlocking(IO.problems.filter(_.codec === "code").map(_.description).result)
filtered should have length 1
filtered.head shouldBe problemDigests(3).description
// This also tests that our conversion from xml to strings works
val xml = connector.runBlocking(IO.problems.map(_.xml).result)
xml should have length problemDigests.length
xml should contain theSameElementsAs problemDigests.map(_.detailsXml.toString())
val result = connector.runBlocking(IO.sizeAndProblemDigest(2))
result._1 should have length 2
result._2 shouldBe problemDigests.length
result._1.head shouldBe problemDigests(3)
result._1(1) shouldBe problemDigests.head
val resultOverLength = connector.runBlocking(IO.sizeAndProblemDigest(10))
resultOverLength._1 should have length 4
resultOverLength._1 should contain theSameElementsAs problemDigests
connector.runBlocking(IO.problems.size.result) shouldBe problemDigests.size
val testProblem = ProblemDatabaseTestProblem(ProblemSources.Unknown)
- Thread.sleep(50)
+ Thread.sleep(15)
connector.runBlocking(IO.problems.size.result) shouldBe problemDigests.size + 1
}
}
case class ProblemDatabaseTestProblem(source: ProblemSources.ProblemSource) extends AbstractProblem(source: ProblemSources.ProblemSource) {
override def summary: String = "This is a test problem! No user should ever see this."
override def description: String = "Wow, this is a nice looking problem. I mean really, just look at it."
}
\ No newline at end of file
diff --git a/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala b/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
index eb108f052..a129c0082 100644
--- a/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
+++ b/integration/src/test/scala/net/shrine/integration/ProblemCreation.scala
@@ -1,131 +1,137 @@
package net.shrine.integration
import java.net.{URL, URLConnection, URLStreamHandler, URLStreamHandlerFactory}
import java.sql.SQLException
import net.shrine.adapter.AbstractQueryRetrievalTestCase.BogusRequest
import net.shrine.adapter._
import net.shrine.adapter.client.{CouldNotParseXmlFromAdapter, HttpErrorCodeFromAdapter}
import net.shrine.adapter.components.QueryNotInDatabase
import net.shrine.adapter.dao.BotDetectedException
import net.shrine.adapter.service.{CouldNotVerifySignature, UnknownRequestType}
import net.shrine.aggregation._
import net.shrine.authentication.{NotAuthenticatedException, NotAuthenticatedProblem}
import net.shrine.authorization.{CouldNotInterpretResponseFromPmCell, CouldNotReachPmCell, ErrorStatusFromDataStewardApp, MissingRequiredRoles}
import net.shrine.broadcaster.CouldNotParseResultsException
import net.shrine.client.HttpResponse
import net.shrine.hms.authorization.HMSNotAuthenticatedProblem
import net.shrine.problem._
import net.shrine.protocol.QueryResult.StatusType
import net.shrine.protocol.query.QueryDefinition
import net.shrine.protocol._
import net.shrine.qep.queries.QepDatabaseProblem
import org.scalatest.{FlatSpec, Matchers}
import slick.driver.H2Driver.api._
import scala.concurrent.duration.FiniteDuration
import scala.xml.{NodeSeq, SAXParseException}
/**
* Created by ty on 8/29/16.
+ * Tests that we can successfully log every problem in the codebase.
+ * Due to the time nature of logging problems, we create the succeed
+ * early loop at the bottom to give every problem a chance at being
+ * created on time.
*/
class ProblemCreation extends FlatSpec with Matchers {
val throwable = new IllegalArgumentException("Boo")
val credential: Credential = Credential("string", isToken = true)
val authInfo = AuthenticationInfo("domain", "username", credential)
val authExecption = AdapterLockoutException(authInfo, "url")
val bogus: ShrineRequest = new BogusRequest
val seconds = new FiniteDuration(10, java.util.concurrent.TimeUnit.SECONDS)
val queryDefinition = QueryDefinition("string", None)
val runQueryRequest = new RunQueryRequest("id", seconds, authInfo, 10, None, None, Set(), queryDefinition)
val saxxException: SAXParseException = new SAXParseException("hey", null)
val xmlResponse: String = ""
val someXml = Heyo!
val teapot: HttpResponse = HttpResponse(418, "body")
val nodeId: NodeId = NodeId("name")
val couldNotParseException: CouldNotParseResultsException = CouldNotParseResultsException(5, "url", "body", throwable)
val queryResult = QueryResult(5l, 5l, None, 5l, None, None, None, StatusType("name", isDone=false), None)
val readyQueryResponse = ReadQueryResultResponse(5l, queryResult)
val foo: NonI2b2ableResponse = new Foo()
- "Problems" should "all be successfully created" in {
+ "Problems" should "all be successfully created and logged" in {
URL.setURLStreamHandlerFactory(new BogusUrlFactory)
- val problemSize = () => Problems.DatabaseConnector.runBlocking(Problems.Queries.size.result)
+ val db = Problems.DatabaseConnector
+ val queries = Problems.Queries
+ val problemSize = () => db.runBlocking(queries.size.result)
problemSize() shouldBe 0
val problems: Seq[AbstractProblem] = Seq(
HttpErrorCodeFromAdapter("url", 5, "string response body"),
CouldNotParseXmlFromAdapter("url", 6, "responseBody", saxxException),
QueryNotFound(10l),
QueryResultNotAvailable(10l),
CouldNotRetrieveQueryFromCrc(10l, throwable),
AdapterLockout(authInfo, authExecption),
CrcCouldNotBeInvoked("crcUrl", bogus, CrcInvocationException("url", bogus, throwable)),
AdapterMappingProblem(AdapterMappingException(runQueryRequest, "message", throwable)),
AdapterDatabaseProblem(new SQLException("reason", "state", 5)),
BotDetected(BotDetectedException("domain", "user", 5l, 5l, 5l)),
CannotParseXmlFromCrc(saxxException, xmlResponse),
ExceptionWhileLoadingCrcResponse(throwable, xmlResponse),
ErrorFromCrcBreakdown(ErrorFromCrcException("message")),
CannotInterpretCrcBreakdownXml(MissingCrCXmlResultException(someXml, throwable)),
QueryNotInDatabase(I2b2AdminReadQueryDefinitionRequest("project", seconds, authInfo, 5l)),
// Difficult to test, as I would have to pull it out of the defining code,
// Change it from an object to a case class, and make sure that I'm not breaking
// Any breakdown logic by doing so.
// BreakdownFailure,
CouldNotVerifySignature(BroadcastMessage(5l, authInfo, bogus)),
UnknownRequestType(RequestType("apple", None)),
NotAuthenticatedProblem(NotAuthenticatedException("string", "string", "message", throwable)),
MissingRequiredRoles("pid", Set(), authInfo),
CouldNotReachPmCell("url", authInfo, throwable),
CouldNotInterpretResponseFromPmCell("url", authInfo, teapot, throwable),
ErrorStatusFromDataStewardApp(spray.http.HttpResponse(), new URL("bogus", "host", 5, "file")),
CouldNotConnectToAdapter(nodeId, throwable),
TimedOutWithAdapter(nodeId),
CouldNotParseResultsProblem(couldNotParseException),
HttpErrorResponseProblem(couldNotParseException),
NoValidResponsesToAggregate(),
// Difficult to test since I can't grab private value:
// InvalidResultProblem(Invalid(None, "error")),
HMSNotAuthenticatedProblem(authInfo),
ErrorStatusFromCrc(None, ""),
QepDatabaseProblem(throwable),
ProblemNotYetEncoded("summary", Some(throwable)),
NoI2b2AnalogExists(foo.getClass),
TestProblem()
)
var count = 0
// give it up to 1 second to finish
- while(problemSize() != problems.length && count < 10) {
- Thread.sleep(100)
+ while(problemSize() != problems.length && count < 20) {
+ Thread.sleep(50)
count+=1
}
problemSize() shouldBe problems.length
- Problems.DatabaseConnector.runBlocking(Problems.Queries.result) should contain theSameElementsAs problems.map(_.toDigest)
+ db.runBlocking(queries.result) should contain theSameElementsAs problems.map(_.toDigest)
}
}
class Foo extends ShrineResponse with NonI2b2ableResponse {
override def toXml: NodeSeq = Yay
}
class BogusUrlFactory extends URLStreamHandlerFactory {
override def createURLStreamHandler(protocol: String): URLStreamHandler = new BogusUrlHandler
}
class BogusUrlHandler extends URLStreamHandler {
override def openConnection(u: URL): URLConnection = new BogusUrlConnection(u)
}
class BogusUrlConnection(u: URL) extends URLConnection(u) {
override def connect(): Unit = {}
}
\ No newline at end of file