namespace NightLight.Core.Tests open NightLight.Core.Core open NightLight.Core.Tests.InteractionListGenerators open NightLight.Core.Tests.InteractionListHelpers open NightLight.Core.Models open FsCheck.Xunit open FsCheck.FSharp type NightLightTests() = let createFakeHomeWithNightLightAndInteract (interactions: Interaction list) = let mutable nightLightStateMachine = NightLightStateMachine() let fakeHome = FakeHome() fakeHome.OnEventPublished.Add(fun event -> match event |> nightLightStateMachine.OnEventReceived with | Ok(newState, commands) -> commands |> Seq.iter fakeHome.ProcessCommand nightLightStateMachine <- newState | Error error -> failwith $"Unexpected error {error}") fakeHome.Interact interactions fakeHome [ |])>] let ``All lights should either be off or have the right color`` (interactions: Interaction list) = let fakeHome = createFakeHomeWithNightLightAndInteract interactions let partOfDay = getPartOfDayAfterInteractions interactions fakeHome.LightStates |> Seq.forall (function | _, Off -> true | _, On(_, color) -> match partOfDay with | Day -> color = White || color = Yellow | Night -> color = Red) |> Prop.collect partOfDay |> Prop.collect $"{fakeHome.LightsThatAreOn.Length} light(s) on" |> Prop.label fakeHome.Label |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) [ |], MaxTest = 500)>] let ``All lights should either be off or have a brightness that fits its color`` (interactions: Interaction list) = let fakeHome = createFakeHomeWithNightLightAndInteract interactions let time = getTimeAfterInteractions interactions |> _.TimeOfDay let alarm = hasNewDayStartedSince interactions (tryGetLastBedroomControllingRemoteInteraction interactions) && startOfDay <= time && time <= endOfAlarm let scaledForAlarm light brightness = if (light = RightBedroomLamp || light = LeftBedroomLamp) && alarm then float brightness * ((time - startOfDay) / (endOfAlarm - startOfDay)) |> byte else brightness fakeHome.LightStates |> Seq.forall (fun (light, state) -> let maybeExpectedBrightness = match (lightProps light).Bulb, state with | _, Off -> None | IkeaBulb, On(_, White) -> Some 254uy | IkeaBulb, On(_, Yellow) -> Some 210uy | IkeaBulb, On(_, Red) -> Some 254uy | PaulmannBulb, On(_, White) -> Some 35uy | PaulmannBulb, On(_, Yellow) -> Some 35uy | PaulmannBulb, On(_, Red) -> Some 80uy |> Option.map (scaledForAlarm light) let maybeActualBrightness = match state with | Off -> None | On(brightness, _) -> Some brightness maybeExpectedBrightness = maybeActualBrightness) |> Prop.collect $"{fakeHome.LightsThatAreOn.Length} light(s) on" |> Prop.classify alarm "alarm" |> Prop.label fakeHome.Label |> Prop.trivial (fakeHome.LightsThatAreOn.Length = 0) [ |], MaxTest = 500)>] let ``All lights with power should have the correct state`` (interactions: Interaction list) = let fakeHome = createFakeHomeWithNightLightAndInteract interactions let lightsWithPower = fakeHome.LightStates |> Seq.filter (fun (light, _) -> doesLightHavePowerAfterInteractions light interactions) |> Seq.toList let lastBedroomControllingRemoteInteraction = tryGetLastBedroomControllingRemoteInteraction interactions let newDayStartedSinceLastBedroomControllingRemoteInteraction = hasNewDayStartedSince interactions lastBedroomControllingRemoteInteraction let livingRoomLightsToggledOn = interactions |> Seq.choose (function | Interaction.LivingRoomControllingRemoteInteraction interaction -> Some interaction | _ -> None) |> Seq.fold (fun state interaction -> match interaction with | RemotePressedRightButton -> not state | LivingRoomRemotePressedOnButton -> true | LivingRoomRemotePressedOffButton -> false) true let isExpectedOn light = match light with | LeftBedroomLamp | RightBedroomLamp -> if newDayStartedSinceLastBedroomControllingRemoteInteraction then true else match lastBedroomControllingRemoteInteraction with | Some(_, RemotePressedOffButton) -> false | Some(_, RemotePressedLeftButton) -> light = LeftBedroomLamp | Some(_, RemotePressedOnButton) -> true | None -> true | LivingRoomWallLamp | LivingRoomFloorLamp -> livingRoomLightsToggledOn | BathroomCeilingLamp -> true lightsWithPower |> Seq.forall (fun (light, state) -> state.IsOn = isExpectedOn light) |> Prop.collect $"last bedroom controlling remote interaction is {lastBedroomControllingRemoteInteraction |> Option.map snd}" |> Prop.collect $"{lightsWithPower.Length} light(s) with power" |> Prop.classify livingRoomLightsToggledOn "living room lights toggled on" |> Prop.classify newDayStartedSinceLastBedroomControllingRemoteInteraction "new day started since last bedroom controlling remote interaction" |> Prop.label fakeHome.Label |> Prop.trivial (lightsWithPower.Length = 0)