1
1
defmodule AshJsonApi.Test do
2
- @ moduledoc false
2
+ @ moduledoc """
3
+ Utilities for testing AshJsonApi.
4
+
5
+ ## Making Requests
6
+
7
+ The request testing functions get/patch/post/delete all support the following options
8
+
9
+ - `:status`: Asserts that the response has the provided status after making the request
10
+ - `:router`: The corresponding JsonApiRouter to go through. Can be set statically in config, see below for more.
11
+ - `:actor`: Sets the provided actor as the actor for the request
12
+ - `:tenant`: Sets the provided tenant as the tenant for the request
13
+
14
+ A standard test would look like this:
15
+
16
+ ```elixir
17
+ test "can list posts", %{current_user: current_user} do
18
+ Domain
19
+ # GET /posts
20
+ # assert resp.status == 200
21
+ |> get("/posts", status: 200, actor: current_user, router: MyAppWeb.JsonApiRouter)
22
+ # pattern match on the data key of the response
23
+ |> assert_data_matches([
24
+ %{
25
+ "attributes" => %{
26
+ "name" => "foo"
27
+ }
28
+ }
29
+ ])
30
+ end
31
+ ```
32
+ """
3
33
use Plug.Test
4
34
5
35
require ExUnit.Assertions
6
36
import ExUnit.Assertions
7
37
8
- # This probably won't work for users of ashjsonapi
9
- @ schema_file "lib/ash_json_api/test/response_schema"
10
- @ external_resource @ schema_file
11
-
38
+ @ doc """
39
+ Sends a GET request to the given path. See the module docs for more.
40
+ """
12
41
def get ( domain , path , opts \\ [ ] ) do
13
42
result =
14
43
:get
15
44
|> conn ( path )
45
+ |> set_req_headers ( opts )
46
+ |> set_context_opts ( opts )
16
47
|> maybe_set_endpoint ( opts )
17
48
|> set_accept_request_header ( opts )
18
- |> AshJsonApi.Domain.Info . router ( domain ) . call (
19
- AshJsonApi.Domain.Info . router ( domain ) . init ( [ ] )
20
- )
49
+ |> call_router ( domain , opts )
21
50
22
51
assert result . state == :sent
23
52
@@ -50,15 +79,18 @@ defmodule AshJsonApi.Test do
50
79
end
51
80
end
52
81
82
+ @ doc """
83
+ Sends a POST request to the given path. See the module docs for more.
84
+ """
53
85
def post ( domain , path , body , opts \\ [ ] ) do
54
86
result =
55
87
:post
56
88
|> conn ( path , Jason . encode! ( body ) )
89
+ |> set_req_headers ( opts )
90
+ |> set_context_opts ( opts )
57
91
|> set_content_type_request_header ( opts )
58
92
|> set_accept_request_header ( opts )
59
- |> AshJsonApi.Domain.Info . router ( domain ) . call (
60
- AshJsonApi.Domain.Info . router ( domain ) . init ( [ ] )
61
- )
93
+ |> call_router ( domain , opts )
62
94
63
95
assert result . state == :sent
64
96
@@ -95,22 +127,25 @@ defmodule AshJsonApi.Test do
95
127
end
96
128
97
129
if Code . ensure_loaded? ( Multipart ) do
130
+ @ doc """
131
+ Sends a multipart POST request to the given path. See the module docs for more.
132
+ """
98
133
def multipart_post ( domain , path , body , opts \\ [ ] ) do
99
134
parser_opts =
100
135
Plug.Parsers . init ( parsers: [ AshJsonApi.Plug.Parser ] , pass: [ "*/*" ] , json_decoder: Jason )
101
136
102
137
result =
103
138
:post
104
139
|> conn ( path , Multipart . body_binary ( body ) )
140
+ |> set_req_headers ( opts )
141
+ |> set_context_opts ( opts )
105
142
|> put_req_header (
106
143
"content-type" ,
107
144
Multipart . content_type ( body , "multipart/x.ash+form-data" )
108
145
)
109
146
|> set_accept_request_header ( opts )
110
147
|> Plug.Parsers . call ( parser_opts )
111
- |> AshJsonApi.Domain.Info . router ( domain ) . call (
112
- AshJsonApi.Domain.Info . router ( domain ) . init ( [ ] )
113
- )
148
+ |> call_router ( domain , opts )
114
149
115
150
assert result . state == :sent
116
151
@@ -157,15 +192,18 @@ defmodule AshJsonApi.Test do
157
192
end
158
193
end
159
194
195
+ @ doc """
196
+ Sends a PATCH request to the given path. See the module docs for more.
197
+ """
160
198
def patch ( domain , path , body , opts \\ [ ] ) do
161
199
result =
162
200
:patch
163
201
|> conn ( path , Jason . encode! ( body ) )
202
+ |> set_req_headers ( opts )
203
+ |> set_context_opts ( opts )
164
204
|> set_content_type_request_header ( opts )
165
205
|> set_accept_request_header ( opts )
166
- |> AshJsonApi.Domain.Info . router ( domain ) . call (
167
- AshJsonApi.Domain.Info . router ( domain ) . init ( [ ] )
168
- )
206
+ |> call_router ( domain , opts )
169
207
170
208
assert result . state == :sent
171
209
@@ -201,14 +239,17 @@ defmodule AshJsonApi.Test do
201
239
end
202
240
end
203
241
242
+ @ doc """
243
+ Sends a DELETE request to the given path. See the module docs for more.
244
+ """
204
245
def delete ( domain , path , opts \\ [ ] ) do
205
246
result =
206
247
:delete
207
248
|> conn ( path )
249
+ |> set_req_headers ( opts )
250
+ |> set_context_opts ( opts )
208
251
|> set_accept_request_header ( opts )
209
- |> AshJsonApi.Domain.Info . router ( domain ) . call (
210
- AshJsonApi.Domain.Info . router ( domain ) . init ( [ ] )
211
- )
252
+ |> call_router ( domain , opts )
212
253
213
254
assert result . state == :sent
214
255
@@ -241,6 +282,9 @@ defmodule AshJsonApi.Test do
241
282
end
242
283
end
243
284
285
+ @ doc """
286
+ Assert that the response body's `"data"` equals an exact value
287
+ """
244
288
defmacro assert_data_equals ( conn , expected_data ) do
245
289
quote do
246
290
conn = unquote ( conn )
@@ -251,6 +295,9 @@ defmodule AshJsonApi.Test do
251
295
end
252
296
end
253
297
298
+ @ doc """
299
+ Assert that the response body's `"data"` matches a pattern
300
+ """
254
301
defmacro assert_data_matches ( conn , data_pattern ) do
255
302
quote do
256
303
conn = unquote ( conn )
@@ -260,6 +307,7 @@ defmodule AshJsonApi.Test do
260
307
end
261
308
end
262
309
310
+ @ doc false
263
311
defmacro assert_meta_equals ( conn , expected_meta ) do
264
312
quote bind_quoted: [ conn: conn , expected_meta: expected_meta ] do
265
313
assert % { "meta" => ^ expected_meta } = conn . resp_body
@@ -268,11 +316,13 @@ defmodule AshJsonApi.Test do
268
316
end
269
317
end
270
318
319
+ @ doc false
271
320
def assert_response_header_equals ( conn , header , value ) do
272
321
assert get_resp_header ( conn , header ) == [ value ]
273
322
conn
274
323
end
275
324
325
+ @ doc false
276
326
defmacro assert_attribute_equals ( conn , attribute , expected_value ) do
277
327
quote bind_quoted: [ attribute: attribute , expected_value: expected_value , conn: conn ] do
278
328
assert % { "data" => % { "attributes" => % { ^ attribute => ^ expected_value } } } = conn . resp_body
@@ -281,6 +331,7 @@ defmodule AshJsonApi.Test do
281
331
end
282
332
end
283
333
334
+ @ doc false
284
335
defmacro assert_id_equals ( conn , expected_value ) do
285
336
quote bind_quoted: [ expected_value: expected_value , conn: conn ] do
286
337
assert % { "data" => % { "id" => ^ expected_value } } = conn . resp_body
@@ -289,15 +340,7 @@ defmodule AshJsonApi.Test do
289
340
end
290
341
end
291
342
292
- @ doc """
293
- Validate the response contains a Resource Object as per 5.2 Specification 1.0
294
-
295
- A resource object MUST contain at least the following top-level members:
296
- - id
297
- - type
298
-
299
- see: https://jsonapi.org/format/1.0/#document-resource-objects
300
- """
343
+ @ doc false
301
344
defmacro assert_valid_resource_object ( conn , expected_type , expected_id ) do
302
345
quote bind_quoted: [ conn: conn , expected_type: expected_type , expected_id: expected_id ] do
303
346
assert % {
@@ -311,6 +354,7 @@ defmodule AshJsonApi.Test do
311
354
end
312
355
end
313
356
357
+ @ doc false
314
358
defmacro assert_valid_resource_objects ( conn , expected_type , expected_ids ) do
315
359
quote bind_quoted: [ conn: conn , expected_type: expected_type , expected_ids: expected_ids ] do
316
360
assert % {
@@ -329,6 +373,7 @@ defmodule AshJsonApi.Test do
329
373
end
330
374
end
331
375
376
+ @ doc false
332
377
defmacro assert_invalid_resource_objects ( conn , expected_type , expected_ids ) do
333
378
quote bind_quoted: [ conn: conn , expected_type: expected_type , expected_ids: expected_ids ] do
334
379
assert % {
@@ -347,6 +392,7 @@ defmodule AshJsonApi.Test do
347
392
end
348
393
end
349
394
395
+ @ doc false
350
396
defmacro assert_attribute_missing ( conn , attribute ) do
351
397
quote bind_quoted: [ conn: conn , attribute: attribute ] do
352
398
assert % { "data" => % { "attributes" => attributes } } = conn . resp_body
@@ -357,6 +403,22 @@ defmodule AshJsonApi.Test do
357
403
end
358
404
end
359
405
406
+ @ doc """
407
+ Asserts that an error is in the response where each key present in the provided map
408
+ has the same value in the error.
409
+
410
+ ## Example
411
+
412
+ ```elixr
413
+ Domain
414
+ |> delete("/posts/1", status: 404)
415
+ |> assert_has_error(%{
416
+ "code" => "not_found",
417
+ "detail" => "No post record found with `id: 1`",
418
+ "title" => "Entity Not Found"
419
+ })
420
+ ```
421
+ """
360
422
defmacro assert_has_error ( conn , fields ) do
361
423
quote do
362
424
assert % { "errors" => [ _ | _ ] = errors } = unquote ( conn ) . resp_body
@@ -371,6 +433,21 @@ defmodule AshJsonApi.Test do
371
433
end
372
434
end
373
435
436
+ @ doc """
437
+ Assert that the given function returns true for at least one included record
438
+
439
+ ## Example
440
+
441
+ Domain
442
+ |> get("/posts/\# {post.id}/?include=author", status: 200)
443
+ |> assert_has_matching_include(fn
444
+ %{"type" => "author", "id" => ^author_id} ->
445
+ true
446
+
447
+ _ ->
448
+ false
449
+ end)
450
+ """
374
451
defmacro assert_has_matching_include ( conn , function ) do
375
452
quote do
376
453
assert % { "included" => included } = unquote ( conn ) . resp_body
@@ -384,6 +461,21 @@ defmodule AshJsonApi.Test do
384
461
end
385
462
end
386
463
464
+ @ doc """
465
+ Refute that the given function returns true for at least one included record
466
+
467
+ ## Example
468
+
469
+ Domain
470
+ |> get("/posts/\# {post.id}/?include=author", status: 200)
471
+ |> refute_has_matching_include(fn
472
+ %{"type" => "author", "id" => ^author_id} ->
473
+ true
474
+
475
+ _ ->
476
+ false
477
+ end)
478
+ """
387
479
defmacro refute_has_matching_include ( conn , function ) do
388
480
quote do
389
481
with % { "included" => included } when is_list ( included ) <- unquote ( conn ) . resp_body do
@@ -396,6 +488,7 @@ defmodule AshJsonApi.Test do
396
488
end
397
489
end
398
490
491
+ @ doc false
399
492
defmacro assert_equal_links ( conn , expected_links ) do
400
493
quote bind_quoted: [ expected_links: expected_links , conn: conn ] do
401
494
% { "links" => resp_links } = conn . resp_body
@@ -411,6 +504,7 @@ defmodule AshJsonApi.Test do
411
504
end
412
505
end
413
506
507
+ @ doc false
414
508
def uri_with_query ( nil ) , do: nil
415
509
416
510
def uri_with_query ( value ) do
@@ -456,4 +550,24 @@ defmodule AshJsonApi.Test do
456
550
conn
457
551
end
458
552
end
553
+
554
+ defp set_context_opts ( conn , opts ) do
555
+ conn
556
+ |> Ash.PlugHelpers . set_actor ( opts [ :actor ] )
557
+ |> Ash.PlugHelpers . set_tenant ( opts [ :tenant ] )
558
+ end
559
+
560
+ defp set_req_headers ( conn , opts ) do
561
+ opts [ :headers ]
562
+ |> Kernel . || ( [ ] )
563
+ |> Enum . reduce ( conn , fn { header , value } , conn ->
564
+ Plug.Conn . put_req_header ( conn , to_string ( header ) , to_string ( value ) )
565
+ end )
566
+ end
567
+
568
+ defp call_router ( conn , domain , opts ) do
569
+ router = opts [ :router ] || AshJsonApi.Domain.Info . router ( domain )
570
+
571
+ router . call ( conn , router . init ( [ ] ) )
572
+ end
459
573
end
0 commit comments