4
4
"""Tests for the frequenz.dispatch.actor package."""
5
5
6
6
import asyncio
7
- from dataclasses import dataclass
7
+ from dataclasses import dataclass , replace
8
8
from datetime import datetime , timedelta , timezone
9
9
from random import randint
10
- from typing import AsyncIterator , Iterator
10
+ from typing import AsyncIterator , Iterator , TypeVar
11
11
from unittest .mock import MagicMock
12
12
13
13
import async_solipsism
16
16
from frequenz .channels ._broadcast import Sender
17
17
from frequenz .client .dispatch .test .client import FakeClient , to_create_params
18
18
from frequenz .client .dispatch .test .generator import DispatchGenerator
19
- from frequenz .client .dispatch .types import Dispatch
19
+ from frequenz .client .dispatch .types import Dispatch , Frequency
20
20
from pytest import fixture
21
21
22
- from frequenz .dispatch .actor import DispatchActor
22
+ from frequenz .dispatch .actor import (
23
+ Created ,
24
+ Deleted ,
25
+ DispatchActor ,
26
+ DispatchEvent ,
27
+ Updated ,
28
+ )
23
29
24
30
25
31
# This method replaces the event loop for all tests in the file.
@@ -45,13 +51,13 @@ def _now() -> datetime:
45
51
return datetime .now (tz = timezone .utc )
46
52
47
53
48
- @dataclass
54
+ @dataclass ( frozen = True )
49
55
class ActorTestEnv :
50
56
"""Test environment for the actor."""
51
57
52
58
actor : DispatchActor
53
59
"""The actor under test."""
54
- updated_dispatches : Receiver [Dispatch ]
60
+ updated_dispatches : Receiver [DispatchEvent ]
55
61
"""The receiver for updated dispatches."""
56
62
ready_dispatches : Receiver [Dispatch ]
57
63
"""The receiver for ready dispatches."""
@@ -64,8 +70,9 @@ class ActorTestEnv:
64
70
@fixture
65
71
async def actor_env () -> AsyncIterator [ActorTestEnv ]:
66
72
"""Return an actor test environment."""
73
+ T = TypeVar ("T" )
67
74
68
- class YieldingSender (Sender [Dispatch ]):
75
+ class YieldingSender (Sender [T ]):
69
76
"""A sender that yields after sending.
70
77
71
78
For testing we want to manipulate the time after a call to send.
@@ -74,15 +81,12 @@ class YieldingSender(Sender[Dispatch]):
74
81
opportunity to manipulate the time.
75
82
"""
76
83
77
- def __init__ (self , channel : Broadcast [Dispatch ]) -> None :
78
- super ().__init__ (channel )
79
-
80
- async def send (self , msg : Dispatch ) -> None :
84
+ async def send (self , msg : T ) -> None :
81
85
"""Send the value and yield."""
82
86
await super ().send (msg )
83
87
await asyncio .sleep (1 )
84
88
85
- updated_dispatches = Broadcast [Dispatch ]("updated_dispatches" )
89
+ updated_dispatches = Broadcast [DispatchEvent ]("updated_dispatches" )
86
90
ready_dispatches = Broadcast [Dispatch ]("ready_dispatches" )
87
91
microgrid_id = randint (1 , 100 )
88
92
@@ -122,13 +126,96 @@ async def test_new_dispatch_created(
122
126
) -> None :
123
127
"""Test that a new dispatch is created."""
124
128
sample = generator .generate_dispatch (actor_env .microgrid_id )
129
+
130
+ await _test_new_dispatch_created (actor_env , sample )
131
+
132
+
133
+ async def _test_new_dispatch_created (
134
+ actor_env : ActorTestEnv ,
135
+ sample : Dispatch ,
136
+ ) -> Dispatch :
137
+ """Test that a new dispatch is created.
138
+
139
+ Args:
140
+ actor_env: The actor environment
141
+ sample: The sample dispatch to create
142
+
143
+ Returns:
144
+ The sample dispatch that was created
145
+ """
125
146
await actor_env .client .create (** to_create_params (sample ))
126
147
127
- dispatch = await actor_env .updated_dispatches .receive ()
128
- sample .update_time = dispatch .update_time
129
- sample .create_time = dispatch .create_time
130
- sample .id = dispatch .id
131
- assert dispatch == sample
148
+ dispatch_event = await actor_env .updated_dispatches .receive ()
149
+
150
+ match dispatch_event :
151
+ case Deleted (dispatch ) | Updated (dispatch ):
152
+ assert False , "Expected a created event"
153
+ case Created (dispatch ):
154
+ sample .update_time = dispatch .update_time
155
+ sample .create_time = dispatch .create_time
156
+ sample .id = dispatch .id
157
+ assert dispatch == sample
158
+
159
+ return sample
160
+
161
+
162
+ async def test_existing_dispatch_updated (
163
+ actor_env : ActorTestEnv ,
164
+ generator : DispatchGenerator ,
165
+ fake_time : time_machine .Coordinates ,
166
+ ) -> None :
167
+ """Test that an existing dispatch is updated."""
168
+ sample = generator .generate_dispatch (actor_env .microgrid_id )
169
+ sample .active = False
170
+ sample .recurrence .frequency = Frequency .DAILY
171
+
172
+ fake_time .shift (timedelta (seconds = 1 ))
173
+
174
+ await _test_new_dispatch_created (actor_env , sample )
175
+ fake_time .shift (timedelta (seconds = 1 ))
176
+
177
+ await actor_env .client .update (
178
+ sample .id ,
179
+ new_fields = {
180
+ "active" : True ,
181
+ "recurrence.frequency" : Frequency .UNSPECIFIED ,
182
+ },
183
+ )
184
+ fake_time .shift (timedelta (seconds = 1 ))
185
+
186
+ dispatch_event = await actor_env .updated_dispatches .receive ()
187
+ match dispatch_event :
188
+ case Created (dispatch ) | Deleted (dispatch ):
189
+ assert False , "Expected an updated event"
190
+ case Updated (dispatch ):
191
+ sample .update_time = dispatch .update_time
192
+ sample .active = True
193
+ sample .recurrence = replace (
194
+ sample .recurrence , frequency = Frequency .UNSPECIFIED
195
+ )
196
+ assert dispatch == sample
197
+
198
+
199
+ async def test_existing_dispatch_deleted (
200
+ actor_env : ActorTestEnv ,
201
+ generator : DispatchGenerator ,
202
+ fake_time : time_machine .Coordinates ,
203
+ ) -> None :
204
+ """Test that an existing dispatch is deleted."""
205
+ sample = generator .generate_dispatch (actor_env .microgrid_id )
206
+
207
+ await _test_new_dispatch_created (actor_env , sample )
208
+
209
+ await actor_env .client .delete (sample .id )
210
+ fake_time .shift (timedelta (seconds = 1 ))
211
+
212
+ dispatch_event = await actor_env .updated_dispatches .receive ()
213
+ match dispatch_event :
214
+ case Created (dispatch ) | Updated (dispatch ):
215
+ assert False , "Expected a deleted event"
216
+ case Deleted (dispatch ):
217
+ sample .create_time = dispatch .create_time
218
+ assert dispatch == sample
132
219
133
220
134
221
async def test_dispatch_schedule (
0 commit comments