Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

implements suspeded state #3

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ ThisBuild / scalacOptions ++= Seq(
ThisBuild / resolvers += "Typesafe Releases" at "https://repo.typesafe.com/typesafe/releases/"

ThisBuild / libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.0.8" % Test
"org.scalatest" %% "scalatest" % "3.1.1" % Test
)

lazy val fsm = project.in(file("."))
Expand All @@ -67,3 +67,9 @@ lazy val oo = project

lazy val fp = project
.settings(name := "phantom-state-machine-fp")
.settings(scalaVersion := "0.22.0-RC1")
.settings(scalacOptions in ThisBuild := Seq(
"-deprecation",
"-feature",
"-unchecked")
)
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@ object Organisation {
sealed trait Enabled extends Status
case object Enabled extends Enabled

sealed trait Suspended extends Status
case object Suspended extends Suspended

sealed trait Disabled extends Status
case object Disabled extends Disabled

type OrganisationDefined = Organisation[Defined]
type OrganisationEnabled = Organisation[Enabled]
type OrganisationDisabled = Organisation[Disabled]
type OrganisationDefined = Organisation[Defined]
type OrganisationEnabled = Organisation[Enabled]
type OrganisationSuspended = Organisation[Suspended]
type OrganisationDisabled = Organisation[Disabled]

def apply(id: String): OrganisationDefined =
new Organisation(id, LocalDateTime.now(), Defined, 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ trait OrganisationService {
def credit(org: OrganisationEnabled)(
amount: BigDecimal
): OrganisationEnabled
def debit(org: OrganisationEnabled)(
def debit[A <: Enabled | Suspended](org: Organisation[A])(
amount: BigDecimal
): OrganisationEnabled
def disable(org: OrganisationEnabled): OrganisationDisabled
): Organisation[A]
def suspend(
org: OrganisationEnabled | OrganisationDisabled
): OrganisationSuspended
def resume(org: OrganisationSuspended): OrganisationEnabled
def disable(
org: OrganisationEnabled | OrganisationSuspended
): OrganisationDisabled

def expedite(id: String)(amount: BigDecimal): OrganisationEnabled =
(define _ andThen approve _ andThen credit)(id)(amount)
Expand All @@ -31,11 +37,21 @@ trait OrganisationServiceImpl extends OrganisationService {
): OrganisationEnabled =
org.copy(balance = org.balance + amount)

override def debit(org: OrganisationEnabled)(
override def debit[A <: Enabled | Suspended](org: Organisation[A])(
amount: BigDecimal
): OrganisationEnabled =
): Organisation[A] =
org.copy(balance = org.balance - amount)

override def disable(org: OrganisationEnabled): OrganisationDisabled =
override def suspend(
org: OrganisationEnabled | OrganisationDisabled
): OrganisationSuspended =
org.copy(status = Suspended)

override def resume(org: OrganisationSuspended): OrganisationEnabled =
org.copy(status = Enabled)

override def disable(
org: OrganisationEnabled | OrganisationSuspended
): OrganisationDisabled =
org.copy(status = Disabled)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import java.time.LocalDateTime
import eu.thoughtway.fsm.fp.domain.model.Organisation.{
Defined,
Disabled,
Enabled
Enabled,
Suspended
}
import org.scalatest.{FunSpec, Matchers, ParallelTestExecution}
import org.scalatest.funspec.AnyFunSpec
import org.scalatest.matchers.should.Matchers

class OrganisationServiceTest
extends FunSpec
with Matchers
with ParallelTestExecution {
import scala.language.implicitConversions

class OrganisationServiceTest extends AnyFunSpec with Matchers {

describe("Given an OrganisationService") {

Expand Down Expand Up @@ -46,6 +47,14 @@ class OrganisationServiceTest
"debit(defined)(10)" shouldNot typeCheck
}

it("should not allow suspend") {
"suspend(defined)" shouldNot compile
}

it("should not allow resume") {
"resume(defined)" shouldNot compile
}

it("should not allow disable") {
"disable(defined)" shouldNot typeCheck
}
Expand Down Expand Up @@ -73,6 +82,10 @@ class OrganisationServiceTest
"approve(approved)" shouldNot typeCheck
}

it("should not allow resume") {
"resume(approved)" shouldNot compile
}

describe(
"When an approved Organisation is credited with a given amount"
) {
Expand Down Expand Up @@ -115,6 +128,86 @@ class OrganisationServiceTest
}
}

describe("When an approved Organisation is suspended") {
val suspended = suspend(approved)

it("should have its id remain unchanged") {
suspended.id should be(approved.id)
}

it("should have its created date remain unchanged") {
suspended.created should be(approved.created)
}

it("should have its status set to Suspended") {
suspended.status should be(Suspended)
}

it("should have its balance remain unchanged") {
suspended.balance should be(approved.balance)
}

it("should not allow credit") {
"val a: String = 1" shouldNot typeCheck
}

it("should not allow debit") {
"debit(10)(suspended)" shouldNot typeCheck
}

it("should not allow suspend") {
"suspend(suspended)" shouldNot compile
}

it("should not allow resume") {
"resume(suspended)" shouldNot compile
}

it("should not allow disable") {
"disable(suspended)" shouldNot compile
}

describe("When a suspended Organisation is debited") {
val debited = debit(suspended)(10)

it("should have its id remain unchanged") {
debited.id should be(suspended.id)
}

it("should have its created date remain unchanged") {
debited.created should be(suspended.created)
}

it("should have its status remain unchanged") {
debited.status should be(suspended.status)
}

it("should have its balance decremented by the given amount") {
debited.balance should be(suspended.balance - 10)
}
}

describe("When a suspended Organisation is resumed") {
val resumed = resume(suspended)

it("should have its id remain unchanged") {
resumed.id should be(suspended.id)
}

it("should have its created date remain unchanged") {
resumed.created should be(suspended.created)
}

it("should have its status set to Enabled") {
resumed.status should be(Enabled)
}

it("should have its balance remain unchanged") {
resumed.balance should be(suspended.balance)
}
}
}

describe("When an approved Organisation is disabled") {
val disabled = disable(approved)

Expand Down
58 changes: 58 additions & 0 deletions oo/src/main/scala/eu/thoughtway/fsm/oo/model/Organisation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ class Organisation(private[this] val _id: String)
private[this] val _disabled = new Disabled()
def disabled: Disabled = _disabled

private[this] val _suspended = new Suspended()
def suspended: Suspended = _suspended

private[this] var _state: State = _defined
def state: State = _state

Expand All @@ -31,6 +34,10 @@ class Organisation(private[this] val _id: String)

override def debit(amount: BigDecimal): Unit = _state.debit(amount)

override def suspend(): Unit = _state.suspend()

override def resume(): Unit = _state.resume()

override def disable(): Unit = _state.disable()

sealed trait State extends Organisation.Organisation
Expand All @@ -48,6 +55,16 @@ class Organisation(private[this] val _id: String)
"Can't debit Organisation in Defined state"
)

override def suspend(): Unit =
throw new IllegalStateException(
"Can't suspend Organisation in Defined state"
)

override def resume(): Unit =
throw new IllegalStateException(
"Can't resume Organisation in Defined state"
)

override def disable(): Unit =
throw new IllegalStateException(
"Can't disable Organisation in Defined state"
Expand All @@ -66,6 +83,38 @@ class Organisation(private[this] val _id: String)
override def debit(amount: BigDecimal): Unit =
_balance = balance - amount

override def suspend(): Unit =
_state = _suspended

override def resume(): Unit =
throw new IllegalStateException(
"Can't resume Organisation in Enabled state"
)

override def disable(): Unit = _state = _disabled
}

final class Suspended extends State {
override def approve(): Unit =
throw new IllegalStateException(
"Can't approve Organisation in Suspended state"
)

override def credit(amount: BigDecimal): Unit =
throw new IllegalStateException(
"Can't credit Organisation in Suspended state"
)

override def debit(amount: BigDecimal): Unit =
_balance = balance - amount

override def suspend(): Unit =
throw new IllegalStateException(
"Can't suspend Organisation in Suspended state"
)

override def resume(): Unit = _state = _enabled

override def disable(): Unit = _state = _disabled
}

Expand All @@ -85,6 +134,13 @@ class Organisation(private[this] val _id: String)
"Can't debit Organisation in Disabled state"
)

override def suspend(): Unit = _state = _suspended

override def resume(): Unit =
throw new IllegalStateException(
"Can't resume Organisation in Disabled state"
)

override def disable(): Unit =
throw new IllegalStateException(
"Can't disable Organisation in Disabled state"
Expand All @@ -97,6 +153,8 @@ object Organisation {
def approve(): Unit
def credit(amount: BigDecimal): Unit
def debit(amount: BigDecimal): Unit
def suspend(): Unit
def resume(): Unit
def disable(): Unit
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ trait OrganisationService {
def approve(org: Organisation): Unit
def credit(org: Organisation, amount: BigDecimal): Unit
def debit(org: Organisation, amount: BigDecimal): Unit
def suspend(org: Organisation): Unit
def resume(org: Organisation): Unit
def disable(org: Organisation): Unit

def expedite(amount: BigDecimal)(id: String): Organisation = {
Expand All @@ -28,5 +30,9 @@ object OrganisationService extends OrganisationService {
override def debit(org: Organisation, amount: BigDecimal): Unit =
org.debit(amount)

override def suspend(org: Organisation): Unit = org.suspend()

override def resume(org: Organisation): Unit = org.resume()

override def disable(org: Organisation): Unit = org.disable()
}
Loading