Skip to content

Commit

Permalink
Fixes #21539: Make campaign test work
Browse files Browse the repository at this point in the history
  • Loading branch information
fanf committed Aug 4, 2022
1 parent 5e9faf6 commit 74a62b8
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ import java.sql.Timestamp
trait CampaignEventRepository {
def get(campaignEventId: CampaignEventId) : IOResult[CampaignEvent]
def saveCampaignEvent(c : CampaignEvent) : IOResult[CampaignEvent]

/*
* Semantic is:
* - if Nil or None, clause is ignored
* - if a value is provided, then it is use to filter things accordingly
*/
def getWithCriteria(states : List[CampaignEventState], campaignType: Option[CampaignType], campaignId : Option[CampaignId]) : IOResult[List[CampaignEvent]]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ import scala.concurrent.duration.Duration


trait Campaign {
def info : CampaignInfo
def details : CampaignDetails
def campaignType : CampaignType
def info : CampaignInfo
def details : CampaignDetails
def campaignType: CampaignType
def copyWithId(newId : CampaignId) : Campaign
}

Expand Down Expand Up @@ -144,11 +144,17 @@ case class WeeklySchedule(
case class OneShot(start : DateTime) extends CampaignSchedule

trait CampaignDetails
trait CampaignType {
def value : String
}

case class CampaignEvent(id : CampaignEventId, campaignId : CampaignId, state : CampaignEventState, start : DateTime, end : DateTime, campaignType : CampaignType )
case class CampaignType(value : String)

case class CampaignEvent(
id : CampaignEventId
, campaignId : CampaignId
, state : CampaignEventState
, start : DateTime
, end : DateTime
, campaignType: CampaignType
)
case class CampaignEventId(value : String)

sealed trait CampaignEventState{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,7 @@ class CampaignSerializer {
}

val campaignTypeBase : PartialFunction[String, CampaignType]= {
case c: String =>
new CampaignType {
def value: String = c
}
case c: String => CampaignType(c)
}


Expand All @@ -95,7 +92,9 @@ class CampaignSerializer {
def parse(string : String) : IOResult[Campaign] = tranlaters.map(_.read()).fold(readBase) { case (a, b) => b orElse a }(string)
def campaignType (string : String) : CampaignType = tranlaters.map(_.campaignType()).fold(campaignTypeBase) { case (a, b) => b orElse a }(string)

}

object CampaignSerializer {
implicit val idEncoder : JsonEncoder[CampaignId]= JsonEncoder[String].contramap(_.value)
implicit val dateTime : JsonEncoder[DateTime] = JsonEncoder[String].contramap(DateFormaterService.serialize)
implicit val dayOfWeek : JsonEncoder[DayOfWeek] = JsonEncoder[Int].contramap(_.value)
Expand All @@ -114,7 +113,6 @@ class CampaignSerializer {
implicit val statusInfoEncoder : JsonEncoder[CampaignStatus] = DeriveJsonEncoder.gen
implicit val scheduleEncoder : JsonEncoder[CampaignSchedule]= DeriveJsonEncoder.gen
implicit val campaignInfoEncoder : JsonEncoder[CampaignInfo]= DeriveJsonEncoder.gen
implicit val typeEncoder : JsonEncoder[CampaignType] = JsonEncoder[String].contramap(_.value)

implicit val idDecoder : JsonDecoder[CampaignId] = JsonDecoder[String].map(s => CampaignId(s))

Expand Down Expand Up @@ -148,19 +146,20 @@ class CampaignSerializer {
}
)

implicit val durationDecoder : JsonDecoder[Duration] = JsonDecoder[Long].map(_.millis)
implicit val statusInfoDecoder : JsonDecoder[CampaignStatus] = DeriveJsonDecoder.gen
implicit val scheduleDecoder : JsonDecoder[CampaignSchedule]= DeriveJsonDecoder.gen
implicit val typeDecoder : JsonDecoder[CampaignType] = JsonDecoder[String].map(campaignType)
implicit val campaignInfoDecoder : JsonDecoder[CampaignInfo]= DeriveJsonDecoder.gen
implicit val durationDecoder : JsonDecoder[Duration] = JsonDecoder[Long].map(_.millis)
implicit val statusInfoDecoder : JsonDecoder[CampaignStatus] = DeriveJsonDecoder.gen
implicit val scheduleDecoder : JsonDecoder[CampaignSchedule]= DeriveJsonDecoder.gen
implicit val campaignTypeDecoder: JsonDecoder[CampaignType] = JsonDecoder[String].map(CampaignType)
implicit val campaignInfoDecoder: JsonDecoder[CampaignInfo]= DeriveJsonDecoder.gen

implicit val campaignEventIdDecoder : JsonDecoder[CampaignEventId] = JsonDecoder[String].map(CampaignEventId)
implicit val campaignEventStateDecoder : JsonDecoder[CampaignEventState] = JsonDecoder[String].mapOrFail(CampaignEventState.parse)
implicit val campaignEventDecoder : JsonDecoder[CampaignEvent] = DeriveJsonDecoder.gen
implicit val campaignEventIdDecoder : JsonDecoder[CampaignEventId] = JsonDecoder[String].map(CampaignEventId)
implicit val campaignEventStateDecoder: JsonDecoder[CampaignEventState] = JsonDecoder[String].mapOrFail(CampaignEventState.parse)
implicit val campaignEventDecoder : JsonDecoder[CampaignEvent] = DeriveJsonDecoder.gen


implicit val campaignEventIdEncoder : JsonEncoder[CampaignEventId] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventStateEncoder : JsonEncoder[CampaignEventState] = JsonEncoder[String].contramap( _.value)
implicit val campaignEventEncoder : JsonEncoder[CampaignEvent] = DeriveJsonEncoder.gen
implicit val campaignTypeEncoder : JsonEncoder[CampaignType] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventIdEncoder : JsonEncoder[CampaignEventId] = JsonEncoder[String].contramap(_.value)
implicit val campaignEventStateEncoder: JsonEncoder[CampaignEventState] = JsonEncoder[String].contramap( _.value)
implicit val campaignEventEncoder : JsonEncoder[CampaignEvent] = DeriveJsonEncoder.gen
}

Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,15 @@
package com.normation.rudder.campaigns

import cats.implicits._

import com.normation.errors.IOResult
import com.normation.errors.Inconsistency
import com.normation.rudder.campaigns.CampaignEventState._
import com.normation.utils.StringUuidGenerator

import com.normation.zio.ZioRuntime
import org.joda.time.DateTime

import zio.Queue
import zio.ZIO
import zio.clock.Clock
Expand Down Expand Up @@ -286,10 +289,10 @@ class MainCampaignService(repo: CampaignEventRepository, campaignRepo: CampaignR
alreadyScheduled <- repo.getWithCriteria(Running :: Scheduled :: Nil, None, None)
campaigns <- campaignRepo.getAll()
_ <- CampaignLogger.debug(s"Got ${campaigns.size} campaigns, check all started")
newEvents <- ZIO.foreach(campaigns.filterNot(c => alreadyScheduled.exists(_.campaignId == c.info.id))) {
c =>
scheduleCampaignEvent(c)
}
toStart = campaigns.filterNot(c => alreadyScheduled.exists(_.campaignId == c.info.id))
newEvents <- ZIO.foreach(toStart) { c =>
scheduleCampaignEvent(c)
}
_ <- CampaignLogger.debug(s"Scheduled ${newEvents.size} new events, queue them")
_ <- ZIO.foreach(newEvents) { ev => s.queueCampaign(ev) }
} yield {
Expand All @@ -304,7 +307,6 @@ class MainCampaignService(repo: CampaignEventRepository, campaignRepo: CampaignR
for {
_ <- CampaignLogger.debug("Starting campaign scheduler")
_ <- s.start().forkDaemon

_ <- CampaignLogger.debug("Starting campaign forked, now getting already created events")
alreadyScheduled <- repo.getWithCriteria(Running :: Scheduled :: Nil, None, None)
_ <- CampaignLogger.debug("Got events, queue them")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
package com.normation.rudder.rest.lift
import com.normation.errors.Unexpected
import com.normation.rudder.api.ApiVersion
import com.normation.rudder.apidata.ZioJsonExtractor
import com.normation.rudder.campaigns.CampaignEvent
Expand All @@ -10,12 +9,13 @@ import com.normation.rudder.campaigns.CampaignId
import com.normation.rudder.campaigns.CampaignLogger
import com.normation.rudder.campaigns.CampaignRepository
import com.normation.rudder.campaigns.CampaignSerializer
import com.normation.rudder.campaigns.CampaignSerializer._
import com.normation.rudder.campaigns.MainCampaignService
import com.normation.rudder.rest.ApiPath
import com.normation.rudder.rest.AuthzToken
import com.normation.rudder.rest.RestExtractorService
import com.normation.rudder.rest.implicits.*
import com.normation.rudder.rest.CampaignApi as API
import com.normation.rudder.rest.implicits._
import com.normation.rudder.rest.{CampaignApi => API}
import com.normation.utils.StringUuidGenerator

import net.liftweb.common.EmptyBox
Expand All @@ -24,7 +24,8 @@ import net.liftweb.http.LiftResponse
import net.liftweb.http.Req

import zio.ZIO
import zio.syntax.*
import zio.syntax._
import com.normation.errors.Unexpected

class CampaignApi (
campaignRepository: CampaignRepository
Expand All @@ -34,7 +35,6 @@ class CampaignApi (
, restExtractorService: RestExtractorService
, stringUuidGenerator: StringUuidGenerator
) extends LiftApiModuleProvider[API] {
import campaignSerializer._

def schemas = API

Expand Down Expand Up @@ -149,7 +149,6 @@ class CampaignApi (
val campaignType = req.params.get("campaignType").flatMap(_.headOption).map(campaignSerializer.campaignType)
val campaignId = req.params.get("campaignId").flatMap(_.headOption).map(i => CampaignId(i))
campaignEventRepository.getWithCriteria(states,campaignType,campaignId).toLiftResponseList(params,schema )

}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ import scala.concurrent.duration.Duration
import scala.util.control.NonFatal
import scala.xml.Elem
import zio.syntax._
import zio.{Tag as _, *}
import zio.{Tag => _, _}
import com.normation.box._
import com.normation.errors.IOResult
import com.normation.errors._
Expand Down Expand Up @@ -193,7 +193,7 @@ object TestActor {
}

object revisionRepo {
import GitVersion._
import com.normation.GitVersion._

val revisionsMap = RefM.make(Map[Revision, RevisionInfo]()).runNow

Expand Down Expand Up @@ -2346,12 +2346,13 @@ class MockSettings(wfservice: WorkflowLevelService, asyncWF: AsyncWorkflowInfo)

// It would be much simpler if the root classes were concrete, parameterized with a A type:
// case class Campaign[A](info: CampaignInfo, details: A) // or even info inlined
case object DumbCampaignType extends CampaignType {
val value = "dumb-campaign"
}
final object DumbCampaignType extends CampaignType("dumb-campaign")

final case class DumbCampaignDetails(name: String) extends CampaignDetails

@jsonDiscriminator("campaignType")
sealed trait DumbCampaignTrait extends Campaign

@jsonHint(DumbCampaignType.value)
final case class DumbCampaign(info: CampaignInfo, details: DumbCampaignDetails) extends DumbCampaignTrait {
val campaignType = DumbCampaignType
Expand Down Expand Up @@ -2390,7 +2391,7 @@ class MockCampaign() {

object dumbCampaignTranslator extends JSONTranslateCampaign {
import zio.json._
import campaignSerializer._
import com.normation.rudder.campaigns.CampaignSerializer._
implicit val dumbCampaignDetailsDecoder : JsonDecoder[DumbCampaignDetails] = DeriveJsonDecoder.gen
implicit val dumbCampaignDecoder : JsonDecoder[DumbCampaignTrait] = DeriveJsonDecoder.gen
implicit val dumbCampaignDetailsEncoder : JsonEncoder[DumbCampaignDetails] = DeriveJsonEncoder.gen
Expand Down Expand Up @@ -2426,7 +2427,9 @@ class MockCampaign() {
items.get.map(_.get(id)).notOptional(s"Campaign event not found: ${id.value}")
}
def saveCampaignEvent(c : CampaignEvent) : IOResult[CampaignEvent] = {
items.update(_ + (c.id -> c)) *> c.succeed
for {
_ <- items.update(map => map + ((c.id, c)))
} yield c
}

def getWithCriteria(states: List[CampaignEventState], campaignType: Option[CampaignType], campaignId: Option[CampaignId]): IOResult[List[CampaignEvent]] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@

package com.normation.rudder.rest

import com.normation.rudder.DumbCampaignType
import com.normation.rudder.campaigns.CampaignEvent
import com.normation.rudder.campaigns.CampaignEventId
import com.normation.rudder.campaigns.CampaignEventState
import com.normation.rudder.campaigns.CampaignId
import com.normation.rudder.campaigns.MainCampaignService
import com.normation.rudder.rest.RudderJsonResponse.JsonRudderApiResponse
import com.normation.rudder.rest.RudderJsonResponse.LiftJsonResponse
Expand All @@ -52,8 +57,11 @@ import org.specs2.mutable.Specification
import org.specs2.runner.JUnitRunner
import org.specs2.specification.AfterAll

import zio.json._
import com.normation.zio._

@RunWith(classOf[JUnitRunner])
class CampaignApiTests extends Specification with AfterAll with Loggable {
class CampaignApiTest extends Specification with AfterAll with Loggable {

val restTestSetUp = RestTestSetUp.newEnv
val restTest = new RestTest(restTestSetUp.liftRules)
Expand All @@ -72,7 +80,7 @@ class CampaignApiTests extends Specification with AfterAll with Loggable {

def children(f: File) = f.children.toList.map(_.name)

// org.slf4j.LoggerFactory.getLogger("api-processing").asInstanceOf[ch.qos.logback.classic.Logger].setLevel(ch.qos.logback.classic.Level.TRACE)
org.slf4j.LoggerFactory.getLogger("campaign").asInstanceOf[ch.qos.logback.classic.Logger].setLevel(ch.qos.logback.classic.Level.TRACE)

sequential

Expand All @@ -83,32 +91,59 @@ class CampaignApiTests extends Specification with AfterAll with Loggable {
|"name":"first campaign",
|"description":"a test campaign present when rudder boot",
|"status":{"value":"enabled"},
|"schedule":{"day":1,"startHour":3,"type":"weekly"},
|"schedule":{"day":1,"startHour":3,"startMinute":42,"type":"weekly"},
|"duration":3600000},
|"details":{"name":"campaign #0"}
|"details":{"name":"campaign #0"},
|"campaignType":"dumb-campaign"
|}""".stripMargin.replaceAll("""\n""","")

// init in mock
val ce0 = CampaignEvent(
CampaignEventId("e0")
, CampaignId("c0")
, CampaignEventState.Finished
, new DateTime(0)
, new DateTime(1)
, DumbCampaignType
)

"have one campaign" in {
val resp = s"""[$c0json]"""

restTest.testGETResponse("/secure/api/campaigns/models") {
restTest.testGETResponse("/secure/api/campaigns") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[zio.json.ast.Json]]]("campaigns").toJson must beEqualTo(resp)
case err => ko(s"I got an error in test: ${err}")
}
}

// THIS TEST IS BROKEN FOR NOW
"and its json format is stable" in {
restTest.testGETResponse("/secure/api/campaigns/events") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[CampaignEvent]]]("campaignEvents") must beEqualTo(List(ce0))
case err => ko(s"I got an error in test: ${err}")
}
}

"have a second one when we trigger a scheduling check" in {
MainCampaignService.start(restTestSetUp.mockCampaign.mainCampaignService)

// the second one won't be C0, but for now, it is not even generated
val resp = s"""[$c0json,$c0json]"""
"have one more campaign event when we trigger a scheduling check" in {
ZioRuntime.unsafeRun(MainCampaignService.start(restTestSetUp.mockCampaign.mainCampaignService))

restTest.testGETResponse("/secure/api/campaigns/models") {
Thread.sleep(50) // this is needed because the start is async and takes a bit of time

restTest.testGETResponse("/secure/api/campaigns/events") {
case Full(LiftJsonResponse(JsonRudderApiResponse(_, _, _, Some(map), _), _, _)) =>
map.asInstanceOf[Map[String, List[zio.json.ast.Json]]]("campaigns").toJson must beEqualTo(resp)
val events = map.asInstanceOf[Map[String, List[CampaignEvent]]]("campaignEvents")

(events.size must beEqualTo(2)) and
{
val next = events.collectFirst { case x if x.id != ce0.id => x }.getOrElse(throw new IllegalArgumentException(s"Missing test value"))
// it's in the future
(next.start.getMillis must be_>(System.currentTimeMillis())) and
(next.state must beEqualTo(CampaignEventState.Scheduled)) and
(next.campaignId must beEqualTo(ce0.campaignId))
}

case err => ko(s"I got an error in test: ${err}")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ import java.nio.file.Files
import java.util.zip.ZipFile
import com.normation.rudder.rest.RestUtils.toJsonResponse
import com.normation.rudder.rest.v1.RestStatus
import net.liftweb.common.{Full, Loggable}
import net.liftweb.http.{InMemoryResponse, Req}
import net.liftweb.json.JsonAST.{JArray, JField, JObject}
import net.liftweb.common._
import net.liftweb.http.InMemoryResponse
import net.liftweb.http.Req
import net.liftweb.json.JsonAST._
import net.liftweb.json.JsonDSL._
import org.apache.commons.io.FileUtils
import org.junit.runner.RunWith
Expand Down Expand Up @@ -524,7 +525,8 @@ class SystemApiTest extends Specification with AfterAll with Loggable {
import net.liftweb.http.js.JsExp._
(code must beEqualTo(500)) and
(json.toJsCmd must beMatching(".*Error when trying to get archive as a Zip: SystemError: Error when retrieving commit revision.*"))
case _ => ko
case x =>
ko
}
}

Expand Down

0 comments on commit 74a62b8

Please sign in to comment.