• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MrThearMan / undine / 16081423623

04 Jul 2025 09:57PM UTC coverage: 97.685%. First build
16081423623

Pull #33

github

web-flow
Merge 6eb57167c into 784a68391
Pull Request #33: Add Subscriptions

1798 of 1841 branches covered (97.66%)

Branch coverage included in aggregate %.

1009 of 1176 new or added lines in 36 files covered. (85.8%)

26853 of 27489 relevant lines covered (97.69%)

8.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.15
/tests/test_hooks.py
1
from __future__ import annotations
9✔
2

3
from typing import AsyncGenerator, Generator
9✔
4

5
import pytest
9✔
6
from graphql import ExecutionResult, GraphQLError
9✔
7

8
from undine.hooks import (
9✔
9
    LifecycleHook,
10
    LifecycleHookContext,
11
    LifecycleHookManager,
12
    use_lifecycle_hooks_async,
13
    use_lifecycle_hooks_sync,
14
)
15

16

17
def get_default_context() -> LifecycleHookContext:
9✔
18
    return LifecycleHookContext(
9✔
19
        source="query { hello }",
20
        document=None,
21
        variables={},
22
        operation_name=None,
23
        extensions={},
24
        request=None,  # type: ignore[arg-type]
25
        result=None,
26
    )
27

28

29
def test_lifecycle_hook() -> None:
9✔
30
    call_stack: list[str] = []
9✔
31

32
    class MyHook(LifecycleHook):
9✔
33
        def run(self) -> Generator[None, None, None]:
9✔
34
            call_stack.append("before")
9✔
35
            yield
9✔
36
            call_stack.append("after")
9✔
37

38
    hook = MyHook(context=get_default_context())
9✔
39

40
    with hook.use_sync():
9✔
41
        call_stack.append("inside")
9✔
42

43
    assert call_stack == ["before", "inside", "after"]
9✔
44

45

46
def test_lifecycle_hook__context() -> None:
9✔
47
    class MyHook(LifecycleHook):
9✔
48
        def run(self) -> Generator[None, None, None]:
9✔
49
            self.context.variables["hello"] = "world"
9✔
50
            yield
9✔
51
            self.context.variables["hello"] = "undine"
9✔
52

53
    context = get_default_context()
9✔
54
    context.variables["hello"] = "you"
9✔
55

56
    hook = MyHook(context=context)
9✔
57

58
    assert context.variables["hello"] == "you"
9✔
59

60
    with hook.use_sync():
9✔
61
        assert context.variables["hello"] == "world"
9✔
62

63
    assert context.variables["hello"] == "undine"
9✔
64

65

66
def test_lifecycle_hook_manager() -> None:
9✔
67
    call_stack: list[str] = []
9✔
68

69
    class MyHook(LifecycleHook):
9✔
70
        def run(self) -> Generator[None, None, None]:
9✔
71
            nonlocal call_stack
72

73
            call_stack.append("my before")
9✔
74
            yield
9✔
75
            call_stack.append("my after")
9✔
76

77
    class YourHook(LifecycleHook):
9✔
78
        def run(self) -> Generator[None, None, None]:
9✔
79
            call_stack.append("your before")
9✔
80
            yield
9✔
81
            call_stack.append("your after")
9✔
82

83
    context = get_default_context()
9✔
84

85
    with LifecycleHookManager(hooks=[MyHook, YourHook], context=context):
9✔
86
        call_stack.append("inside")
9✔
87

88
    assert call_stack == ["my before", "your before", "inside", "your after", "my after"]
9✔
89

90

91
def test_use_lifecycle_hooks_sync() -> None:
9✔
92
    call_stack: list[str] = []
9✔
93

94
    class MyHook(LifecycleHook):
9✔
95
        def run(self) -> Generator[None, None, None]:
9✔
96
            call_stack.append("before")
9✔
97
            yield
9✔
98
            call_stack.append("after")
9✔
99

100
    @use_lifecycle_hooks_sync(hooks=[MyHook])
9✔
101
    def func(context: LifecycleHookContext) -> None:
9✔
102
        call_stack.append("inside")
9✔
103

104
    ctx = get_default_context()
9✔
105

106
    func(ctx)
9✔
107

108
    assert call_stack == ["before", "inside", "after"]
9✔
109

110

111
def test_use_lifecycle_hooks_sync__error_raised() -> None:
9✔
112
    call_stack: list[str] = []
9✔
113

114
    class MyHook(LifecycleHook):
9✔
115
        def run(self) -> Generator[None, None, None]:
9✔
116
            call_stack.append("before")
9✔
117
            yield
9✔
118
            call_stack.append("after")
×
119

120
    @use_lifecycle_hooks_sync(hooks=[MyHook])
9✔
121
    def func(context: LifecycleHookContext) -> None:
9✔
122
        call_stack.append("inside")
9✔
123
        msg = "Error"
9✔
124
        raise ValueError(msg)
9✔
125

126
    ctx = get_default_context()
9✔
127

128
    with pytest.raises(ValueError, match="Error"):
9✔
129
        func(ctx)
9✔
130

131
    assert call_stack == ["before", "inside"]
9✔
132

133
    assert ctx.result is None
9✔
134

135

136
def test_use_lifecycle_hooks_sync__error_raised__catch() -> None:
9✔
137
    call_stack: list[str] = []
9✔
138

139
    class MyHook(LifecycleHook):
9✔
140
        def run(self) -> Generator[None, None, None]:
9✔
141
            call_stack.append("before")
9✔
142
            try:
9✔
143
                yield
9✔
144
            except ValueError as error:
9✔
145
                call_stack.append("after")
9✔
146
                self.context.result = ExecutionResult(errors=[GraphQLError(str(error))])
9✔
147

148
    @use_lifecycle_hooks_sync(hooks=[MyHook])
9✔
149
    def func(context: LifecycleHookContext) -> None:
9✔
150
        call_stack.append("inside")
9✔
151
        msg = "Error"
9✔
152
        raise ValueError(msg)
9✔
153

154
    ctx = get_default_context()
9✔
155

156
    func(ctx)
9✔
157

158
    assert call_stack == ["before", "inside", "after"]
6✔
159

160
    assert isinstance(ctx.result, ExecutionResult)
6✔
161
    assert ctx.result.data is None
6✔
162
    assert ctx.result.errors == [GraphQLError("Error")]
6✔
163

164

165
@pytest.mark.asyncio
9✔
166
async def test_use_lifecycle_hooks_async() -> None:
9✔
167
    call_stack: list[str] = []
9✔
168

169
    class MyHook(LifecycleHook):
9✔
170
        def run(self) -> Generator[None, None, None]:
9✔
171
            call_stack.append("before")
9✔
172
            yield
9✔
173
            call_stack.append("after")
9✔
174

175
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
176
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
177
        call_stack.append("inside")
9✔
178

179
    ctx = get_default_context()
9✔
180

181
    await func(ctx)
9✔
182

183
    assert call_stack == ["before", "inside", "after"]
9✔
184

185

186
@pytest.mark.asyncio
9✔
187
async def test_use_lifecycle_hooks_async__error_raised() -> None:
9✔
188
    call_stack: list[str] = []
9✔
189

190
    class MyHook(LifecycleHook):
9✔
191
        def run(self) -> Generator[None, None, None]:
9✔
192
            call_stack.append("before")
9✔
193
            yield
9✔
NEW
194
            call_stack.append("after")
×
195

196
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
197
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
198
        call_stack.append("inside")
9✔
199
        msg = "Error"
9✔
200
        raise ValueError(msg)
9✔
201

202
    ctx = get_default_context()
9✔
203

204
    with pytest.raises(ValueError, match="Error"):
9✔
205
        await func(ctx)
9✔
206

207
    assert call_stack == ["before", "inside"]
9✔
208

209

210
@pytest.mark.asyncio
9✔
211
async def test_use_lifecycle_hooks_async__error_raised__catch() -> None:
9✔
212
    call_stack: list[str] = []
9✔
213

214
    class MyHook(LifecycleHook):
9✔
215
        def run(self) -> Generator[None, None, None]:
9✔
216
            call_stack.append("before")
9✔
217
            try:
9✔
218
                yield
9✔
219
            except ValueError as error:
9✔
220
                call_stack.append("after")
9✔
221
                self.context.result = ExecutionResult(errors=[GraphQLError(str(error))])
9✔
222

223
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
224
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
225
        call_stack.append("inside")
9✔
226
        msg = "Error"
9✔
227
        raise ValueError(msg)
9✔
228

229
    ctx = get_default_context()
9✔
230

231
    await func(ctx)
9✔
232

233
    assert call_stack == ["before", "inside", "after"]
9✔
234

235
    assert isinstance(ctx.result, ExecutionResult)
9✔
236
    assert ctx.result.data is None
9✔
237
    assert ctx.result.errors == [GraphQLError("Error")]
9✔
238

239

240
@pytest.mark.asyncio
9✔
241
async def test_use_lifecycle_hooks_async__run_async() -> None:
9✔
242
    call_stack: list[str] = []
9✔
243

244
    class MyHook(LifecycleHook):
9✔
245
        def run(self) -> Generator[None, None, None]:
9✔
246
            call_stack.append("before")
×
NEW
247
            yield
×
NEW
248
            call_stack.append("after")
×
249

250
        async def run_async(self) -> AsyncGenerator[None, None]:
9✔
251
            call_stack.append("before async")
9✔
252
            yield
9✔
253
            call_stack.append("after async")
9✔
254

255
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
256
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
257
        call_stack.append("inside")
9✔
258

259
    ctx = get_default_context()
9✔
260

261
    await func(ctx)
9✔
262

263
    assert call_stack == ["before async", "inside", "after async"]
9✔
264

265

266
@pytest.mark.asyncio
9✔
267
async def test_use_lifecycle_hooks_async__run_async__error_raised() -> None:
9✔
268
    call_stack: list[str] = []
9✔
269

270
    class MyHook(LifecycleHook):
9✔
271
        def run(self) -> Generator[None, None, None]:
9✔
NEW
272
            call_stack.append("before")
×
NEW
273
            yield
×
NEW
274
            call_stack.append("after")
×
275

276
        async def run_async(self) -> AsyncGenerator[None, None]:
9✔
277
            call_stack.append("before async")
9✔
278
            yield
9✔
NEW
279
            call_stack.append("after async")
×
280

281
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
282
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
283
        call_stack.append("inside")
9✔
284
        msg = "Error"
9✔
285
        raise ValueError(msg)
9✔
286

287
    ctx = get_default_context()
9✔
288

289
    with pytest.raises(ValueError, match="Error"):
9✔
290
        await func(ctx)
9✔
291

292
    assert call_stack == ["before async", "inside"]
9✔
293

294

295
@pytest.mark.asyncio
9✔
296
async def test_use_lifecycle_hooks_async__run_async__error_raised__catch() -> None:
9✔
297
    call_stack: list[str] = []
9✔
298

299
    class MyHook(LifecycleHook):
9✔
300
        def run(self) -> Generator[None, None, None]:
9✔
NEW
301
            call_stack.append("before")
×
302
            try:
×
303
                yield
×
304
            except ValueError as error:
×
305
                call_stack.append("after")
×
306
                self.context.result = ExecutionResult(errors=[GraphQLError(str(error))])
×
307

308
        async def run_async(self) -> AsyncGenerator[None, None]:
9✔
309
            call_stack.append("before async")
9✔
310
            try:
9✔
311
                yield
9✔
312
            except ValueError as error:
9✔
313
                call_stack.append("after async")
9✔
314
                self.context.result = ExecutionResult(errors=[GraphQLError(str(error))])
9✔
315

316
    @use_lifecycle_hooks_async(hooks=[MyHook])
9✔
317
    async def func(context: LifecycleHookContext) -> None:  # noqa: RUF029
9✔
318
        call_stack.append("inside")
9✔
319
        msg = "Error"
9✔
320
        raise ValueError(msg)
9✔
321

322
    ctx = get_default_context()
9✔
323

324
    await func(ctx)
9✔
325

326
    assert call_stack == ["before async", "inside", "after async"]
9✔
327

328
    assert isinstance(ctx.result, ExecutionResult)
9✔
329
    assert ctx.result.data is None
9✔
330
    assert ctx.result.errors == [GraphQLError("Error")]
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc