deep merge in config update
[akkoma] / test / web / admin_api / config_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.ConfigTest do
6 use Pleroma.DataCase, async: true
7 import Pleroma.Factory
8 alias Pleroma.Web.AdminAPI.Config
9
10 test "get_by_key/1" do
11 config = insert(:config)
12 insert(:config)
13
14 assert config == Config.get_by_params(%{group: config.group, key: config.key})
15 end
16
17 test "create/1" do
18 {:ok, config} = Config.create(%{group: "pleroma", key: "some_key", value: "some_value"})
19 assert config == Config.get_by_params(%{group: "pleroma", key: "some_key"})
20 end
21
22 test "update/1" do
23 config = insert(:config)
24 {:ok, updated} = Config.update(config, %{value: "some_value"})
25 loaded = Config.get_by_params(%{group: config.group, key: config.key})
26 assert loaded == updated
27 end
28
29 describe "update_or_create/1" do
30 test "common" do
31 config = insert(:config)
32 key2 = "another_key"
33
34 params = [
35 %{group: "pleroma", key: key2, value: "another_value"},
36 %{group: config.group, key: config.key, value: "new_value"}
37 ]
38
39 assert Repo.all(Config) |> length() == 1
40
41 Enum.each(params, &Config.update_or_create(&1))
42
43 assert Repo.all(Config) |> length() == 2
44
45 config1 = Config.get_by_params(%{group: config.group, key: config.key})
46 config2 = Config.get_by_params(%{group: "pleroma", key: key2})
47
48 assert config1.value == Config.transform("new_value")
49 assert config2.value == Config.transform("another_value")
50 end
51
52 test "partial update" do
53 config = insert(:config, value: Config.to_binary(key1: "val1", key2: :val2))
54
55 {:ok, _config} =
56 Config.update_or_create(%{
57 group: config.group,
58 key: config.key,
59 value: [key1: :val1, key3: :val3]
60 })
61
62 updated = Config.get_by_params(%{group: config.group, key: config.key})
63
64 value = Config.from_binary(updated.value)
65 assert length(value) == 3
66 assert value[:key1] == :val1
67 assert value[:key2] == :val2
68 assert value[:key3] == :val3
69 end
70
71 test "deep merge" do
72 config = insert(:config, value: Config.to_binary(key1: "val1", key2: [k1: :v1, k2: "v2"]))
73
74 {:ok, config} =
75 Config.update_or_create(%{
76 group: config.group,
77 key: config.key,
78 value: [key1: :val1, key2: [k2: :v2, k3: :v3], key3: :val3]
79 })
80
81 updated = Config.get_by_params(%{group: config.group, key: config.key})
82
83 assert config.value == updated.value
84
85 value = Config.from_binary(updated.value)
86 assert value[:key1] == :val1
87 assert value[:key2] == [k1: :v1, k2: :v2, k3: :v3]
88 assert value[:key3] == :val3
89 end
90
91 test "only full update for some keys" do
92 config1 = insert(:config, key: ":ecto_repos", value: Config.to_binary(repo: Pleroma.Repo))
93 config2 = insert(:config, group: ":cors_plug", key: ":max_age", value: Config.to_binary(18))
94
95 {:ok, _config} =
96 Config.update_or_create(%{
97 group: config1.group,
98 key: config1.key,
99 value: [another_repo: [Pleroma.Repo]]
100 })
101
102 {:ok, _config} =
103 Config.update_or_create(%{
104 group: config2.group,
105 key: config2.key,
106 value: 777
107 })
108
109 updated1 = Config.get_by_params(%{group: config1.group, key: config1.key})
110 updated2 = Config.get_by_params(%{group: config2.group, key: config2.key})
111
112 assert Config.from_binary(updated1.value) == [another_repo: [Pleroma.Repo]]
113 assert Config.from_binary(updated2.value) == 777
114 end
115
116 test "full update if value is not keyword" do
117 config =
118 insert(:config,
119 group: ":tesla",
120 key: ":adapter",
121 value: Config.to_binary(Tesla.Adapter.Hackney)
122 )
123
124 {:ok, _config} =
125 Config.update_or_create(%{
126 group: config.group,
127 key: config.key,
128 value: Tesla.Adapter.Httpc
129 })
130
131 updated = Config.get_by_params(%{group: config.group, key: config.key})
132
133 assert Config.from_binary(updated.value) == Tesla.Adapter.Httpc
134 end
135 end
136
137 test "delete/1" do
138 config = insert(:config)
139 {:ok, _} = Config.delete(%{key: config.key, group: config.group})
140 refute Config.get_by_params(%{key: config.key, group: config.group})
141 end
142
143 describe "transform/1" do
144 test "string" do
145 binary = Config.transform("value as string")
146 assert binary == :erlang.term_to_binary("value as string")
147 assert Config.from_binary(binary) == "value as string"
148 end
149
150 test "boolean" do
151 binary = Config.transform(false)
152 assert binary == :erlang.term_to_binary(false)
153 assert Config.from_binary(binary) == false
154 end
155
156 test "nil" do
157 binary = Config.transform(nil)
158 assert binary == :erlang.term_to_binary(nil)
159 assert Config.from_binary(binary) == nil
160 end
161
162 test "integer" do
163 binary = Config.transform(150)
164 assert binary == :erlang.term_to_binary(150)
165 assert Config.from_binary(binary) == 150
166 end
167
168 test "atom" do
169 binary = Config.transform(":atom")
170 assert binary == :erlang.term_to_binary(:atom)
171 assert Config.from_binary(binary) == :atom
172 end
173
174 test "ssl options" do
175 binary = Config.transform([":tlsv1", ":tlsv1.1", ":tlsv1.2"])
176 assert binary == :erlang.term_to_binary([:tlsv1, :"tlsv1.1", :"tlsv1.2"])
177 assert Config.from_binary(binary) == [:tlsv1, :"tlsv1.1", :"tlsv1.2"]
178 end
179
180 test "pleroma module" do
181 binary = Config.transform("Pleroma.Bookmark")
182 assert binary == :erlang.term_to_binary(Pleroma.Bookmark)
183 assert Config.from_binary(binary) == Pleroma.Bookmark
184 end
185
186 test "pleroma string" do
187 binary = Config.transform("Pleroma")
188 assert binary == :erlang.term_to_binary("Pleroma")
189 assert Config.from_binary(binary) == "Pleroma"
190 end
191
192 test "phoenix module" do
193 binary = Config.transform("Phoenix.Socket.V1.JSONSerializer")
194 assert binary == :erlang.term_to_binary(Phoenix.Socket.V1.JSONSerializer)
195 assert Config.from_binary(binary) == Phoenix.Socket.V1.JSONSerializer
196 end
197
198 test "tesla module" do
199 binary = Config.transform("Tesla.Adapter.Hackney")
200 assert binary == :erlang.term_to_binary(Tesla.Adapter.Hackney)
201 assert Config.from_binary(binary) == Tesla.Adapter.Hackney
202 end
203
204 test "ExSyslogger module" do
205 binary = Config.transform("ExSyslogger")
206 assert binary == :erlang.term_to_binary(ExSyslogger)
207 assert Config.from_binary(binary) == ExSyslogger
208 end
209
210 test "Quack.Logger module" do
211 binary = Config.transform("Quack.Logger")
212 assert binary == :erlang.term_to_binary(Quack.Logger)
213 assert Config.from_binary(binary) == Quack.Logger
214 end
215
216 test "sigil" do
217 binary = Config.transform("~r[comp[lL][aA][iI][nN]er]")
218 assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/)
219 assert Config.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/
220 end
221
222 test "link sigil" do
223 binary = Config.transform("~r/https:\/\/example.com/")
224 assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/)
225 assert Config.from_binary(binary) == ~r/https:\/\/example.com/
226 end
227
228 test "link sigil with um modifiers" do
229 binary = Config.transform("~r/https:\/\/example.com/um")
230 assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/um)
231 assert Config.from_binary(binary) == ~r/https:\/\/example.com/um
232 end
233
234 test "link sigil with i modifier" do
235 binary = Config.transform("~r/https:\/\/example.com/i")
236 assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/i)
237 assert Config.from_binary(binary) == ~r/https:\/\/example.com/i
238 end
239
240 test "link sigil with s modifier" do
241 binary = Config.transform("~r/https:\/\/example.com/s")
242 assert binary == :erlang.term_to_binary(~r/https:\/\/example.com/s)
243 assert Config.from_binary(binary) == ~r/https:\/\/example.com/s
244 end
245
246 test "raise if valid delimiter not found" do
247 assert_raise ArgumentError, "valid delimiter for Regex expression not found", fn ->
248 Config.transform("~r/https://[]{}<>\"'()|example.com/s")
249 end
250 end
251
252 test "2 child tuple" do
253 binary = Config.transform(%{"tuple" => ["v1", ":v2"]})
254 assert binary == :erlang.term_to_binary({"v1", :v2})
255 assert Config.from_binary(binary) == {"v1", :v2}
256 end
257
258 test "proxy tuple with localhost" do
259 binary =
260 Config.transform(%{
261 "tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
262 })
263
264 assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, :localhost, 1234}})
265 assert Config.from_binary(binary) == {:proxy_url, {:socks5, :localhost, 1234}}
266 end
267
268 test "proxy tuple with domain" do
269 binary =
270 Config.transform(%{
271 "tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
272 })
273
274 assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, 'domain.com', 1234}})
275 assert Config.from_binary(binary) == {:proxy_url, {:socks5, 'domain.com', 1234}}
276 end
277
278 test "proxy tuple with ip" do
279 binary =
280 Config.transform(%{
281 "tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
282 })
283
284 assert binary == :erlang.term_to_binary({:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}})
285 assert Config.from_binary(binary) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
286 end
287
288 test "tuple with n childs" do
289 binary =
290 Config.transform(%{
291 "tuple" => [
292 "v1",
293 ":v2",
294 "Pleroma.Bookmark",
295 150,
296 false,
297 "Phoenix.Socket.V1.JSONSerializer"
298 ]
299 })
300
301 assert binary ==
302 :erlang.term_to_binary(
303 {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
304 )
305
306 assert Config.from_binary(binary) ==
307 {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
308 end
309
310 test "tuple with dispatch key" do
311 binary = Config.transform(%{"tuple" => [":dispatch", ["{:_,
312 [
313 {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
314 {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket,
315 {Phoenix.Transports.WebSocket,
316 {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}},
317 {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
318 ]}"]]})
319
320 assert binary ==
321 :erlang.term_to_binary(
322 {:dispatch,
323 [
324 {:_,
325 [
326 {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
327 {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
328 {Phoenix.Transports.WebSocket,
329 {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}},
330 {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
331 ]}
332 ]}
333 )
334
335 assert Config.from_binary(binary) ==
336 {:dispatch,
337 [
338 {:_,
339 [
340 {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
341 {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
342 {Phoenix.Transports.WebSocket,
343 {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}},
344 {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
345 ]}
346 ]}
347 end
348
349 test "map with string key" do
350 binary = Config.transform(%{"key" => "value"})
351 assert binary == :erlang.term_to_binary(%{"key" => "value"})
352 assert Config.from_binary(binary) == %{"key" => "value"}
353 end
354
355 test "map with atom key" do
356 binary = Config.transform(%{":key" => "value"})
357 assert binary == :erlang.term_to_binary(%{key: "value"})
358 assert Config.from_binary(binary) == %{key: "value"}
359 end
360
361 test "list of strings" do
362 binary = Config.transform(["v1", "v2", "v3"])
363 assert binary == :erlang.term_to_binary(["v1", "v2", "v3"])
364 assert Config.from_binary(binary) == ["v1", "v2", "v3"]
365 end
366
367 test "list of modules" do
368 binary = Config.transform(["Pleroma.Repo", "Pleroma.Activity"])
369 assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity])
370 assert Config.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity]
371 end
372
373 test "list of atoms" do
374 binary = Config.transform([":v1", ":v2", ":v3"])
375 assert binary == :erlang.term_to_binary([:v1, :v2, :v3])
376 assert Config.from_binary(binary) == [:v1, :v2, :v3]
377 end
378
379 test "list of mixed values" do
380 binary =
381 Config.transform([
382 "v1",
383 ":v2",
384 "Pleroma.Repo",
385 "Phoenix.Socket.V1.JSONSerializer",
386 15,
387 false
388 ])
389
390 assert binary ==
391 :erlang.term_to_binary([
392 "v1",
393 :v2,
394 Pleroma.Repo,
395 Phoenix.Socket.V1.JSONSerializer,
396 15,
397 false
398 ])
399
400 assert Config.from_binary(binary) == [
401 "v1",
402 :v2,
403 Pleroma.Repo,
404 Phoenix.Socket.V1.JSONSerializer,
405 15,
406 false
407 ]
408 end
409
410 test "simple keyword" do
411 binary = Config.transform([%{"tuple" => [":key", "value"]}])
412 assert binary == :erlang.term_to_binary([{:key, "value"}])
413 assert Config.from_binary(binary) == [{:key, "value"}]
414 assert Config.from_binary(binary) == [key: "value"]
415 end
416
417 test "keyword with partial_chain key" do
418 binary =
419 Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
420
421 assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
422 assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
423 end
424
425 test "keyword" do
426 binary =
427 Config.transform([
428 %{"tuple" => [":types", "Pleroma.PostgresTypes"]},
429 %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
430 %{"tuple" => [":migration_lock", nil]},
431 %{"tuple" => [":key1", 150]},
432 %{"tuple" => [":key2", "string"]}
433 ])
434
435 assert binary ==
436 :erlang.term_to_binary(
437 types: Pleroma.PostgresTypes,
438 telemetry_event: [Pleroma.Repo.Instrumenter],
439 migration_lock: nil,
440 key1: 150,
441 key2: "string"
442 )
443
444 assert Config.from_binary(binary) == [
445 types: Pleroma.PostgresTypes,
446 telemetry_event: [Pleroma.Repo.Instrumenter],
447 migration_lock: nil,
448 key1: 150,
449 key2: "string"
450 ]
451 end
452
453 test "complex keyword with nested mixed childs" do
454 binary =
455 Config.transform([
456 %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
457 %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
458 %{"tuple" => [":link_name", true]},
459 %{"tuple" => [":proxy_remote", false]},
460 %{"tuple" => [":common_map", %{":key" => "value"}]},
461 %{
462 "tuple" => [
463 ":proxy_opts",
464 [
465 %{"tuple" => [":redirect_on_failure", false]},
466 %{"tuple" => [":max_body_length", 1_048_576]},
467 %{
468 "tuple" => [
469 ":http",
470 [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}]
471 ]
472 }
473 ]
474 ]
475 }
476 ])
477
478 assert binary ==
479 :erlang.term_to_binary(
480 uploader: Pleroma.Uploaders.Local,
481 filters: [Pleroma.Upload.Filter.Dedupe],
482 link_name: true,
483 proxy_remote: false,
484 common_map: %{key: "value"},
485 proxy_opts: [
486 redirect_on_failure: false,
487 max_body_length: 1_048_576,
488 http: [
489 follow_redirect: true,
490 pool: :upload
491 ]
492 ]
493 )
494
495 assert Config.from_binary(binary) ==
496 [
497 uploader: Pleroma.Uploaders.Local,
498 filters: [Pleroma.Upload.Filter.Dedupe],
499 link_name: true,
500 proxy_remote: false,
501 common_map: %{key: "value"},
502 proxy_opts: [
503 redirect_on_failure: false,
504 max_body_length: 1_048_576,
505 http: [
506 follow_redirect: true,
507 pool: :upload
508 ]
509 ]
510 ]
511 end
512
513 test "common keyword" do
514 binary =
515 Config.transform([
516 %{"tuple" => [":level", ":warn"]},
517 %{"tuple" => [":meta", [":all"]]},
518 %{"tuple" => [":path", ""]},
519 %{"tuple" => [":val", nil]},
520 %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
521 ])
522
523 assert binary ==
524 :erlang.term_to_binary(
525 level: :warn,
526 meta: [:all],
527 path: "",
528 val: nil,
529 webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
530 )
531
532 assert Config.from_binary(binary) == [
533 level: :warn,
534 meta: [:all],
535 path: "",
536 val: nil,
537 webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
538 ]
539 end
540
541 test "complex keyword with sigil" do
542 binary =
543 Config.transform([
544 %{"tuple" => [":federated_timeline_removal", []]},
545 %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
546 %{"tuple" => [":replace", []]}
547 ])
548
549 assert binary ==
550 :erlang.term_to_binary(
551 federated_timeline_removal: [],
552 reject: [~r/comp[lL][aA][iI][nN]er/],
553 replace: []
554 )
555
556 assert Config.from_binary(binary) ==
557 [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []]
558 end
559
560 test "complex keyword with tuples with more than 2 values" do
561 binary =
562 Config.transform([
563 %{
564 "tuple" => [
565 ":http",
566 [
567 %{
568 "tuple" => [
569 ":key1",
570 [
571 %{
572 "tuple" => [
573 ":_",
574 [
575 %{
576 "tuple" => [
577 "/api/v1/streaming",
578 "Pleroma.Web.MastodonAPI.WebsocketHandler",
579 []
580 ]
581 },
582 %{
583 "tuple" => [
584 "/websocket",
585 "Phoenix.Endpoint.CowboyWebSocket",
586 %{
587 "tuple" => [
588 "Phoenix.Transports.WebSocket",
589 %{
590 "tuple" => [
591 "Pleroma.Web.Endpoint",
592 "Pleroma.Web.UserSocket",
593 []
594 ]
595 }
596 ]
597 }
598 ]
599 },
600 %{
601 "tuple" => [
602 ":_",
603 "Phoenix.Endpoint.Cowboy2Handler",
604 %{"tuple" => ["Pleroma.Web.Endpoint", []]}
605 ]
606 }
607 ]
608 ]
609 }
610 ]
611 ]
612 }
613 ]
614 ]
615 }
616 ])
617
618 assert binary ==
619 :erlang.term_to_binary(
620 http: [
621 key1: [
622 _: [
623 {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
624 {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
625 {Phoenix.Transports.WebSocket,
626 {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
627 {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
628 ]
629 ]
630 ]
631 )
632
633 assert Config.from_binary(binary) == [
634 http: [
635 key1: [
636 {:_,
637 [
638 {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
639 {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
640 {Phoenix.Transports.WebSocket,
641 {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
642 {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
643 ]}
644 ]
645 ]
646 ]
647 end
648 end
649 end