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