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