First
[anni] / test / pleroma / config_db_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 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: ":goose", key: ":level", value: :info)
20     insert(:config, group: ":goose", key: ":meta", value: [:none])
21
22     insert(:config,
23       group: ":goose",
24       key: ":webhook_url",
25       value: "https://gander.com/"
26     )
27
28     config = ConfigDB.get_all_as_keyword()
29
30     assert config[:pleroma] == [
31              {saved.key, saved.value}
32            ]
33
34     assert config[:goose][:level] == :info
35     assert config[:goose][:meta] == [:none]
36     assert config[:goose][:webhook_url] == "https://gander.com/"
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", ":tlsv1.3"]) == [
242                :tlsv1,
243                :"tlsv1.1",
244                :"tlsv1.2",
245                :"tlsv1.3"
246              ]
247     end
248
249     test "pleroma module" do
250       assert ConfigDB.to_elixir_types("Pleroma.Bookmark") == Pleroma.Bookmark
251     end
252
253     test "pleroma string" do
254       assert ConfigDB.to_elixir_types("Pleroma") == "Pleroma"
255     end
256
257     test "phoenix module" do
258       assert ConfigDB.to_elixir_types("Phoenix.Socket.V1.JSONSerializer") ==
259                Phoenix.Socket.V1.JSONSerializer
260     end
261
262     test "tesla module" do
263       assert ConfigDB.to_elixir_types("Tesla.Adapter.Hackney") == Tesla.Adapter.Hackney
264     end
265
266     test "ExSyslogger module" do
267       assert ConfigDB.to_elixir_types("ExSyslogger") == ExSyslogger
268     end
269
270     test "Swoosh.Adapters modules" do
271       assert ConfigDB.to_elixir_types("Swoosh.Adapters.SMTP") == Swoosh.Adapters.SMTP
272       assert ConfigDB.to_elixir_types("Swoosh.Adapters.AmazonSES") == Swoosh.Adapters.AmazonSES
273     end
274
275     test "sigil" do
276       assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/
277     end
278
279     test "link sigil" do
280       assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/
281     end
282
283     test "link sigil with um modifiers" do
284       assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") ==
285                ~r/https:\/\/example.com/um
286     end
287
288     test "link sigil with i modifier" do
289       assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i
290     end
291
292     test "link sigil with s modifier" do
293       assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s
294     end
295
296     test "raise if valid delimiter not found" do
297       assert_raise ArgumentError, "valid delimiter for Regex expression not found", fn ->
298         ConfigDB.to_elixir_types("~r/https://[]{}<>\"'()|example.com/s")
299       end
300     end
301
302     test "2 child tuple" do
303       assert ConfigDB.to_elixir_types(%{"tuple" => ["v1", ":v2"]}) == {"v1", :v2}
304     end
305
306     test "proxy tuple with localhost" do
307       assert ConfigDB.to_elixir_types(%{
308                "tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]
309              }) == {:proxy_url, {:socks5, :localhost, 1234}}
310     end
311
312     test "proxy tuple with domain" do
313       assert ConfigDB.to_elixir_types(%{
314                "tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]
315              }) == {:proxy_url, {:socks5, 'domain.com', 1234}}
316     end
317
318     test "proxy tuple with ip" do
319       assert ConfigDB.to_elixir_types(%{
320                "tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]
321              }) == {:proxy_url, {:socks5, {127, 0, 0, 1}, 1234}}
322     end
323
324     test "tuple with n childs" do
325       assert ConfigDB.to_elixir_types(%{
326                "tuple" => [
327                  "v1",
328                  ":v2",
329                  "Pleroma.Bookmark",
330                  150,
331                  false,
332                  "Phoenix.Socket.V1.JSONSerializer"
333                ]
334              }) == {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer}
335     end
336
337     test "map with string key" do
338       assert ConfigDB.to_elixir_types(%{"key" => "value"}) == %{"key" => "value"}
339     end
340
341     test "map with atom key" do
342       assert ConfigDB.to_elixir_types(%{":key" => "value"}) == %{key: "value"}
343     end
344
345     test "list of strings" do
346       assert ConfigDB.to_elixir_types(["v1", "v2", "v3"]) == ["v1", "v2", "v3"]
347     end
348
349     test "list of modules" do
350       assert ConfigDB.to_elixir_types(["Pleroma.Repo", "Pleroma.Activity"]) == [
351                Pleroma.Repo,
352                Pleroma.Activity
353              ]
354     end
355
356     test "list of atoms" do
357       assert ConfigDB.to_elixir_types([":v1", ":v2", ":v3"]) == [:v1, :v2, :v3]
358     end
359
360     test "list of mixed values" do
361       assert ConfigDB.to_elixir_types([
362                "v1",
363                ":v2",
364                "Pleroma.Repo",
365                "Phoenix.Socket.V1.JSONSerializer",
366                15,
367                false
368              ]) == [
369                "v1",
370                :v2,
371                Pleroma.Repo,
372                Phoenix.Socket.V1.JSONSerializer,
373                15,
374                false
375              ]
376     end
377
378     test "simple keyword" do
379       assert ConfigDB.to_elixir_types([%{"tuple" => [":key", "value"]}]) == [key: "value"]
380     end
381
382     test "keyword" do
383       assert ConfigDB.to_elixir_types([
384                %{"tuple" => [":types", "Pleroma.PostgresTypes"]},
385                %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]},
386                %{"tuple" => [":migration_lock", nil]},
387                %{"tuple" => [":key1", 150]},
388                %{"tuple" => [":key2", "string"]}
389              ]) == [
390                types: Pleroma.PostgresTypes,
391                telemetry_event: [Pleroma.Repo.Instrumenter],
392                migration_lock: nil,
393                key1: 150,
394                key2: "string"
395              ]
396     end
397
398     test "trandformed keyword" do
399       assert ConfigDB.to_elixir_types(a: 1, b: 2, c: "string") == [a: 1, b: 2, c: "string"]
400     end
401
402     test "complex keyword with nested mixed childs" do
403       assert ConfigDB.to_elixir_types([
404                %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]},
405                %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]},
406                %{"tuple" => [":link_name", true]},
407                %{"tuple" => [":proxy_remote", false]},
408                %{"tuple" => [":common_map", %{":key" => "value"}]},
409                %{
410                  "tuple" => [
411                    ":proxy_opts",
412                    [
413                      %{"tuple" => [":redirect_on_failure", false]},
414                      %{"tuple" => [":max_body_length", 1_048_576]},
415                      %{
416                        "tuple" => [
417                          ":http",
418                          [
419                            %{"tuple" => [":follow_redirect", true]},
420                            %{"tuple" => [":pool", ":upload"]}
421                          ]
422                        ]
423                      }
424                    ]
425                  ]
426                }
427              ]) == [
428                uploader: Pleroma.Uploaders.Local,
429                filters: [Pleroma.Upload.Filter.Dedupe],
430                link_name: true,
431                proxy_remote: false,
432                common_map: %{key: "value"},
433                proxy_opts: [
434                  redirect_on_failure: false,
435                  max_body_length: 1_048_576,
436                  http: [
437                    follow_redirect: true,
438                    pool: :upload
439                  ]
440                ]
441              ]
442     end
443
444     test "common keyword" do
445       assert ConfigDB.to_elixir_types([
446                %{"tuple" => [":level", ":warn"]},
447                %{"tuple" => [":meta", [":all"]]},
448                %{"tuple" => [":path", ""]},
449                %{"tuple" => [":val", nil]},
450                %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]}
451              ]) == [
452                level: :warn,
453                meta: [:all],
454                path: "",
455                val: nil,
456                webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE"
457              ]
458     end
459
460     test "complex keyword with sigil" do
461       assert ConfigDB.to_elixir_types([
462                %{"tuple" => [":federated_timeline_removal", []]},
463                %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]},
464                %{"tuple" => [":replace", []]}
465              ]) == [
466                federated_timeline_removal: [],
467                reject: [~r/comp[lL][aA][iI][nN]er/],
468                replace: []
469              ]
470     end
471
472     test "complex keyword with tuples with more than 2 values" do
473       assert ConfigDB.to_elixir_types([
474                %{
475                  "tuple" => [
476                    ":http",
477                    [
478                      %{
479                        "tuple" => [
480                          ":key1",
481                          [
482                            %{
483                              "tuple" => [
484                                ":_",
485                                [
486                                  %{
487                                    "tuple" => [
488                                      "/api/v1/streaming",
489                                      "Pleroma.Web.MastodonAPI.WebsocketHandler",
490                                      []
491                                    ]
492                                  },
493                                  %{
494                                    "tuple" => [
495                                      "/websocket",
496                                      "Phoenix.Endpoint.CowboyWebSocket",
497                                      %{
498                                        "tuple" => [
499                                          "Phoenix.Transports.WebSocket",
500                                          %{
501                                            "tuple" => [
502                                              "Pleroma.Web.Endpoint",
503                                              "Pleroma.Web.UserSocket",
504                                              []
505                                            ]
506                                          }
507                                        ]
508                                      }
509                                    ]
510                                  },
511                                  %{
512                                    "tuple" => [
513                                      ":_",
514                                      "Phoenix.Endpoint.Cowboy2Handler",
515                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]}
516                                    ]
517                                  }
518                                ]
519                              ]
520                            }
521                          ]
522                        ]
523                      }
524                    ]
525                  ]
526                }
527              ]) == [
528                http: [
529                  key1: [
530                    {:_,
531                     [
532                       {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
533                       {"/websocket", Phoenix.Endpoint.CowboyWebSocket,
534                        {Phoenix.Transports.WebSocket,
535                         {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}},
536                       {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
537                     ]}
538                  ]
539                ]
540              ]
541     end
542   end
543 end