first commit

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-28 09:16:54 +01:00
commit 32a7ba8e8f
168 files changed
+123856

No files matched your search

File diff suppressed because it is too large. Load diff
+162
View File
@@ -0,0 +1,162 @@
[
{
"left": "",
"right": "",
"unified": ""
},
{
"left": "",
"right": "a\nb",
"unified": "--- left\n+++ right\n@@ -0,0 +1,2 @@\n+a\n+b"
},
{
"left": "a\nb",
"right": "",
"unified": "--- left\n+++ right\n@@ -1,2 +0,0 @@\n-a\n-b"
},
{
"left": "a\nb\nc",
"right": "a\nx\nc",
"unified": "--- left\n+++ right\n@@ -1,3 +1,3 @@\n a\n-b\n+x\n c"
},
{
"left": "a\na\nb\na",
"right": "a\nb\na\na",
"unified": "--- left\n+++ right\n@@ -1,4 +1,4 @@\n-a\n a\n b\n a\n+a"
},
{
"left": "\n",
"right": "x\n",
"unified": "--- left\n+++ right\n@@ -1 +1 @@\n-\n+x"
},
{
"left": "a\n\nc",
"right": "a\nx\nc",
"unified": "--- left\n+++ right\n@@ -1,3 +1,3 @@\n a\n-\n+x\n c"
},
{
"left": "x\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\na\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx",
"right": "x\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nb\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx\nx",
"unified": "--- left\n+++ right\n@@ -248,254 +248,254 @@\n x\n x\n x\n-a\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n-x\n+b\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x\n+x"
},
{
"left": "alpha\n\nbeta\nbeta\n/ip address\nbeta\ndelta\nalpha\nbeta",
"right": "alpha\n\nbeta\nbeta\n/ip address\nbeta\nalpha\nbeta",
"unified": "--- left\n+++ right\n@@ -4,6 +4,5 @@\n beta\n /ip address\n beta\n-delta\n alpha\n beta"
},
{
"left": "/ip address\n\nalpha\nalpha\nadd address=10.0.0.1\ndelta\nbeta\n\ndelta\nadd address=10.0.0.1\nadd address=10.0.0.1\nbeta\ngamma\ndelta\n\nbeta\nbeta\ndelta\n\ngamma\ndelta\nbeta\ngamma\ngamma\ngamma\nalpha",
"right": "/ip address\n\nalpha\nalpha\nalpha\nadd address=10.0.0.1\ndelta\nbeta\n\ndelta\nadd address=10.0.0.1\nadd address=10.0.0.1\nbeta\ngamma\ndelta\n\nbeta\nbeta\ndelta\n\ngamma\ndelta\nbeta\ngamma\ngamma\ngamma\nalpha",
"unified": "--- left\n+++ right\n@@ -1,5 +1,6 @@\n /ip address\n \n+alpha\n alpha\n alpha\n add address=10.0.0.1"
},
{
"left": "gamma\n/ip address\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\nbeta\ngamma\nalpha\ngamma\nbeta\n/ip address\n\ndelta\ngamma\n/ip address\ngamma\nadd address=10.0.0.1",
"right": "gamma\n/ip address\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\nbeta\ngamma\nalpha\ngamma\nbeta\nbeta\n/ip address\n\ngamma\n/ip address\ngamma\nadd address=10.0.0.1",
"unified": "--- left\n+++ right\n@@ -8,9 +8,9 @@\n alpha\n gamma\n beta\n+beta\n /ip address\n \n-delta\n gamma\n /ip address\n gamma"
},
{
"left": "add address=10.0.0.1\ngamma\n/ip address\nbeta\nalpha\nbeta\ndelta\n/ip address\n\ngamma\ngamma\n\nbeta\n/ip address\n\nalpha\n/ip address\nadd address=10.0.0.1\n/ip address\n/ip address\nalpha\nbeta\nalpha\ndelta\nbeta\n\n/ip address",
"right": "add address=10.0.0.1\ngamma\n/ip address\nbeta\nalpha\nbeta\ndelta\n\n/ip address\ngamma\ngamma\n\nbeta\n/ip address\n\nalpha\n/ip address\nadd address=10.0.0.1\n/ip address\nalpha\nbeta\nalpha\ndelta\nbeta\n\n/ip address",
"unified": "--- left\n+++ right\n@@ -5,8 +5,8 @@\n alpha\n beta\n delta\n+\n /ip address\n-\n gamma\n gamma\n \n@@ -17,7 +17,6 @@\n /ip address\n add address=10.0.0.1\n /ip address\n-/ip address\n alpha\n beta\n alpha"
},
{
"left": "\n/ip address\n/ip address\ngamma\n",
"right": "alpha\n\n/ip address\nbeta\n\n",
"unified": "--- left\n+++ right\n@@ -1,4 +1,5 @@\n+alpha\n \n /ip address\n-/ip address\n-gamma\n+beta\n+"
},
{
"left": "beta\nbeta\nalpha\n/ip address\nbeta\nbeta\nbeta\n/ip address\n/ip address\n\ndelta\n\nadd address=10.0.0.1\nadd address=10.0.0.1\nadd address=10.0.0.1\ngamma\nbeta\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\ndelta",
"right": "beta\nalpha\n/ip address\nbeta\nbeta\nnew\nbeta\n/ip address\n/ip address\ndelta\nadd address=10.0.0.1\nalpha\nadd address=10.0.0.1\nadd address=10.0.0.1\nbeta\ngamma\nbeta\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\ndelta",
"unified": "--- left\n+++ right\n@@ -1,18 +1,18 @@\n-beta\n beta\n alpha\n /ip address\n beta\n beta\n+new\n beta\n /ip address\n /ip address\n-\n delta\n-\n+add address=10.0.0.1\n+alpha\n add address=10.0.0.1\n add address=10.0.0.1\n-add address=10.0.0.1\n+beta\n gamma\n beta\n add address=10.0.0.1"
},
{
"left": "alpha\nbeta\ndelta\n\ndelta\n/ip address\n/ip address\nalpha\nbeta\nadd address=10.0.0.1\nalpha\n/ip address\nadd address=10.0.0.1\nadd address=10.0.0.1\n\ngamma\ndelta\nalpha\nalpha\ngamma\nbeta\n\n/ip address",
"right": "alpha\nbeta\ndelta\n\ndelta\n\nbeta\n/ip address\nalpha\nbeta\nbeta\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\nadd address=10.0.0.1\n\ngamma\ndelta\nalpha\nalpha\ngamma\nbeta\n\n/ip address",
"unified": "--- left\n+++ right\n@@ -3,12 +3,13 @@\n delta\n \n delta\n-/ip address\n+\n+beta\n /ip address\n alpha\n beta\n+beta\n add address=10.0.0.1\n-alpha\n /ip address\n add address=10.0.0.1\n add address=10.0.0.1"
},
{
"left": "beta\nbeta\nbeta\ndelta\nbeta\nbeta\n\nalpha\n\ndelta\nadd address=10.0.0.1\ndelta\nadd address=10.0.0.1\ngamma\nalpha\nalpha\nalpha\nadd address=10.0.0.1\ngamma\nbeta\n\nalpha\nadd address=10.0.0.1\ngamma\nalpha\nalpha\ngamma\ndelta\n\nadd address=10.0.0.1\nbeta\n/ip address\nbeta\n\nbeta\ngamma\ngamma\n\nalpha",
"right": "beta\nbeta\nbeta\ndelta\nbeta\nbeta\n\nalpha\n\ndelta\nadd address=10.0.0.1\ngamma\nalpha\nalpha\nalpha\nadd address=10.0.0.1\ngamma\nbeta\n\nalpha\nadd address=10.0.0.1\ngamma\nalpha\nalpha\ngamma\nadd address=10.0.0.1\nbeta\n/ip address\nbeta\nalpha\n\nnew\nbeta\ngamma\ngamma",
"unified": "--- left\n+++ right\n@@ -7,8 +7,6 @@\n \n alpha\n \n-delta\n-add address=10.0.0.1\n delta\n add address=10.0.0.1\n gamma\n@@ -25,15 +23,13 @@\n alpha\n alpha\n gamma\n-delta\n-\n add address=10.0.0.1\n beta\n /ip address\n beta\n+alpha\n \n+new\n beta\n gamma\n gamma\n-\n-alpha"
},
{
"left": "add address=10.0.0.1\nbeta\nalpha\n/ip address\nbeta\n/ip address\ngamma\nadd address=10.0.0.1\nbeta\n/ip address\nalpha\ngamma\nalpha\ndelta\nbeta\ndelta\n/ip address\n\ngamma\ngamma",
"right": "beta\n/ip address\nbeta\n/ip address\ngamma\nadd address=10.0.0.1\nbeta\nalpha\ngamma\nalpha\nbeta\n\ndelta\n/ip address\n\nnew\nalpha\ngamma\ngamma",
"unified": "--- left\n+++ right\n@@ -1,20 +1,19 @@\n-add address=10.0.0.1\n beta\n-alpha\n /ip address\n beta\n /ip address\n gamma\n add address=10.0.0.1\n beta\n-/ip address\n alpha\n gamma\n alpha\n-delta\n beta\n+\n delta\n /ip address\n \n+new\n+alpha\n gamma\n gamma"
},
{
"left": "beta\nalpha\nbeta\ngamma\ngamma\nalpha\nadd address=10.0.0.1\ngamma\ndelta",
"right": "new\nbeta\nalpha\nbeta\ngamma\ngamma\nalpha\nadd address=10.0.0.1\ngamma\ndelta",
"unified": "--- left\n+++ right\n@@ -1,3 +1,4 @@\n+new\n beta\n alpha\n beta"
},
{
"left": "gamma\nalpha",
"right": "gamma\nnew",
"unified": "--- left\n+++ right\n@@ -1,2 +1,2 @@\n gamma\n-alpha\n+new"
},
{
"left": "/ip address\ndelta\nbeta\ngamma\ndelta\nadd address=10.0.0.1\ndelta\n/ip address\nadd address=10.0.0.1\nbeta\n\nadd address=10.0.0.1\ndelta\n/ip address\nadd address=10.0.0.1\nadd address=10.0.0.1\ndelta\ngamma",
"right": "/ip address\n\ndelta\n\nbeta\ngamma\ndelta\ndelta\nbeta\n/ip address\nalpha\nadd address=10.0.0.1\nbeta\n\nadd address=10.0.0.1\ndelta\n/ip address\nadd address=10.0.0.1\nalpha\ngamma",
"unified": "--- left\n+++ right\n@@ -1,11 +1,14 @@\n /ip address\n+\n delta\n+\n beta\n gamma\n delta\n-add address=10.0.0.1\n delta\n+beta\n /ip address\n+alpha\n add address=10.0.0.1\n beta\n \n@@ -13,6 +16,5 @@\n delta\n /ip address\n add address=10.0.0.1\n-add address=10.0.0.1\n-delta\n+alpha\n gamma"
},
{
"left": "beta\nalpha\nbeta\ndelta\n\nalpha\nalpha\nadd address=10.0.0.1\nalpha\ndelta\nadd address=10.0.0.1\nadd address=10.0.0.1\nadd address=10.0.0.1\n/ip address\ndelta\nadd address=10.0.0.1\nbeta\n/ip address\nalpha\nadd address=10.0.0.1\nbeta",
"right": "beta\nalpha\nalpha\nbeta\ndelta\n\nalpha\nadd address=10.0.0.1\nalpha\ndelta\nadd address=10.0.0.1\nadd address=10.0.0.1\nadd address=10.0.0.1\nnew\n/ip address\ndelta\nadd address=10.0.0.1\n/ip address\nalpha\nadd address=10.0.0.1\nbeta",
"unified": "--- left\n+++ right\n@@ -1,9 +1,9 @@\n beta\n+alpha\n alpha\n beta\n delta\n \n-alpha\n alpha\n add address=10.0.0.1\n alpha\n@@ -11,10 +11,10 @@\n add address=10.0.0.1\n add address=10.0.0.1\n add address=10.0.0.1\n+new\n /ip address\n delta\n add address=10.0.0.1\n-beta\n /ip address\n alpha\n add address=10.0.0.1"
},
{
"left": "/ip address\ngamma\n/ip address\n/ip address\ndelta\ndelta\n/ip address\nbeta\n\n\nadd address=10.0.0.1\nbeta\nalpha\ngamma\nbeta\ngamma\n/ip address\nbeta\n/ip address\ndelta\ndelta\ndelta\n\nalpha\n/ip address\ndelta\ngamma\ndelta",
"right": "/ip address\nbeta\ngamma\nbeta\n/ip address\n/ip address\ndelta\ndelta\n/ip address\nbeta\n\n\nadd address=10.0.0.1\nbeta\nalpha\ngamma\nbeta\n/ip address\nbeta\ndelta\ndelta\ndelta\n\nnew\nalpha\ndelta\ngamma\ndelta",
"unified": "--- left\n+++ right\n@@ -1,5 +1,7 @@\n /ip address\n+beta\n gamma\n+beta\n /ip address\n /ip address\n delta\n@@ -13,16 +15,14 @@\n alpha\n gamma\n beta\n-gamma\n /ip address\n beta\n-/ip address\n delta\n delta\n delta\n \n+new\n alpha\n-/ip address\n delta\n gamma\n delta"
},
{
"left": "add address=10.0.0.1\nalpha\n\nalpha\n\nadd address=10.0.0.1\n\ngamma\n\n\n/ip address\nbeta\nadd address=10.0.0.1\n/ip address\nadd address=10.0.0.1\n/ip address",
"right": "add address=10.0.0.1\nalpha\n\nalpha\n\nadd address=10.0.0.1\n\ngamma\n\n\n/ip address\nbeta\nbeta\nadd address=10.0.0.1\nalpha\n/ip address\nadd address=10.0.0.1\n/ip address\n",
"unified": "--- left\n+++ right\n@@ -10,7 +10,9 @@\n \n /ip address\n beta\n+beta\n add address=10.0.0.1\n+alpha\n /ip address\n add address=10.0.0.1\n /ip address"
},
{
"left": "alpha\nbeta\nalpha\nalpha\ndelta\nadd address=10.0.0.1\n\nalpha\ngamma\nalpha\n\n/ip address\n/ip address\ngamma\nalpha\n/ip address\ngamma\n/ip address\nbeta\ngamma\nbeta",
"right": "alpha\nbeta\nalpha\nalpha\ndelta\nadd address=10.0.0.1\n\nalpha\ngamma\nalpha\n\n/ip address\n/ip address\ngamma\nbeta\nalpha\n/ip address\ngamma\n/ip address\nbeta\ngamma",
"unified": "--- left\n+++ right\n@@ -12,10 +12,10 @@\n /ip address\n /ip address\n gamma\n+beta\n alpha\n /ip address\n gamma\n /ip address\n beta\n gamma\n-beta"
},
{
"left": "delta\nbeta\nadd address=10.0.0.1\nadd address=10.0.0.1\n\nbeta\ndelta\n\nbeta\ngamma",
"right": "beta\nadd address=10.0.0.1\n\n\nbeta\ndelta\n\ngamma",
"unified": "--- left\n+++ right\n@@ -1,10 +1,8 @@\n-delta\n beta\n add address=10.0.0.1\n-add address=10.0.0.1\n+\n \n beta\n delta\n \n-beta\n gamma"
},
{
"left": "\n\nadd address=10.0.0.1\ndelta\nbeta\ngamma\ngamma\nbeta\ndelta\ndelta\nalpha\ngamma\nalpha\ndelta",
"right": "beta\nalpha\n\n\nadd address=10.0.0.1\ndelta\ngamma\ngamma\nbeta\ndelta\ndelta\nalpha\ngamma\nalpha\ndelta",
"unified": "--- left\n+++ right\n@@ -1,8 +1,9 @@\n+beta\n+alpha\n \n \n add address=10.0.0.1\n delta\n-beta\n gamma\n gamma\n beta"
},
{
"left": "",
"right": "alpha",
"unified": "--- left\n+++ right\n@@ -0,0 +1 @@\n+alpha"
},
{
"left": "/ip address\n/ip address\n\nbeta\n/ip address\nalpha",
"right": "/ip address\n\n\nbeta\nalpha",
"unified": "--- left\n+++ right\n@@ -1,6 +1,5 @@\n-/ip address\n /ip address\n \n+\n beta\n-/ip address\n alpha"
},
{
"left": "\n/ip address\n\ngamma\nalpha\nalpha\n/ip address\n/ip address\n\n/ip address\nbeta\n\nadd address=10.0.0.1\nadd address=10.0.0.1\n\n\ndelta\nalpha\ngamma\n/ip address\nbeta\nbeta\nbeta\nalpha\n/ip address\nalpha\n\nalpha\nadd address=10.0.0.1\ndelta\ngamma\ndelta\ndelta\nadd address=10.0.0.1\nadd address=10.0.0.1\nadd address=10.0.0.1\nbeta\nbeta",
"right": "\n/ip address\n\ngamma\nalpha\nalpha\n/ip address\n/ip address\n\n/ip address\nbeta\n\nadd address=10.0.0.1\nadd address=10.0.0.1\n\ndelta\nalpha\ngamma\n/ip address\nbeta\nbeta\nbeta\nalpha\n/ip address\nalpha\n\nalpha\nadd address=10.0.0.1\ndelta\ngamma\ndelta\ndelta\n\nadd address=10.0.0.1\nadd address=10.0.0.1\nadd address=10.0.0.1\nbeta\nbeta",
"unified": "--- left\n+++ right\n@@ -12,7 +12,6 @@\n \n add address=10.0.0.1\n add address=10.0.0.1\n-\n \n delta\n alpha\n@@ -31,6 +30,7 @@\n gamma\n delta\n delta\n+\n add address=10.0.0.1\n add address=10.0.0.1\n add address=10.0.0.1"
},
{
"left": "add address=10.0.0.1\nadd address=10.0.0.1\n/ip address\n\nadd address=10.0.0.1\nbeta\nbeta\ndelta\nadd address=10.0.0.1\ngamma\nadd address=10.0.0.1",
"right": "/ip address\nnew\n\nadd address=10.0.0.1\nbeta\ndelta\nadd address=10.0.0.1",
"unified": "--- left\n+++ right\n@@ -1,11 +1,7 @@\n-add address=10.0.0.1\n-add address=10.0.0.1\n /ip address\n+new\n \n add address=10.0.0.1\n beta\n-beta\n delta\n add address=10.0.0.1\n-gamma\n-add address=10.0.0.1"
},
{
"left": "gamma",
"right": "",
"unified": "--- left\n+++ right\n@@ -1 +0,0 @@\n-gamma"
},
{
"left": "add address=10.0.0.1\nadd address=10.0.0.1\n\nalpha\ngamma\nbeta\nalpha\nalpha\ndelta\nbeta\nbeta\n/ip address\nbeta\nalpha\n/ip address\nbeta\nbeta\nbeta\n/ip address\n/ip address\n\ngamma\nbeta\nbeta\ngamma\ngamma\ngamma\ndelta\nadd address=10.0.0.1\n\n/ip address",
"right": "add address=10.0.0.1\nadd address=10.0.0.1\nalpha\ngamma\nbeta\nalpha\ndelta\nbeta\nbeta\n/ip address\nbeta\nalpha\n/ip address\nbeta\nbeta\nbeta\n/ip address\n\ngamma\nbeta\ngamma\ngamma\ndelta\nadd address=10.0.0.1\n\n/ip address",
"unified": "--- left\n+++ right\n@@ -1,10 +1,8 @@\n add address=10.0.0.1\n add address=10.0.0.1\n-\n alpha\n gamma\n beta\n-alpha\n alpha\n delta\n beta\n@@ -17,12 +15,9 @@\n beta\n beta\n /ip address\n-/ip address\n \n gamma\n beta\n-beta\n-gamma\n gamma\n gamma\n delta"
}
]
+126
View File
@@ -0,0 +1,126 @@
//! The explicit importer must preserve source paths and never start on help/error.
use std::{fs, path::PathBuf, process::Command};
struct Fixture {
_temporary: tempfile::TempDir,
root: PathBuf,
launcher: PathBuf,
arguments: PathBuf,
environment: PathBuf,
}
impl Fixture {
fn new() -> Self {
let temporary = tempfile::tempdir().unwrap();
let root = temporary.path().to_path_buf();
let project = root.join("new application");
fs::create_dir(&project).unwrap();
let launcher = project.join("import-python.sh");
fs::write(&launcher, include_str!("../import-python.sh")).unwrap();
fs::write(
project.join("start.sh"),
"#!/usr/bin/env bash\nset -euo pipefail\nprintf '%s\\0' \"$@\" > \"$IMPORT_TEST_ARGS\"\nprintf '%s\\0' \"${DATA_DIR-}\" \"${DATABASE_PATH-}\" > \"$IMPORT_TEST_ENV\"\nexit \"${IMPORT_TEST_STATUS:-0}\"\n",
)
.unwrap();
Self {
_temporary: temporary,
launcher,
arguments: root.join("arguments"),
environment: root.join("environment"),
root,
}
}
fn command(&self) -> Command {
let mut command = Command::new("bash");
command
.arg(&self.launcher)
.current_dir(&self.root)
.env("IMPORT_TEST_ARGS", &self.arguments)
.env("IMPORT_TEST_ENV", &self.environment)
.env_remove("IMPORT_TEST_STATUS");
command
}
fn arguments(&self) -> Vec<String> {
let mut bytes = fs::read(&self.arguments).unwrap();
assert_eq!(bytes.pop(), Some(0));
bytes
.split(|byte| *byte == 0)
.map(|part| String::from_utf8(part.to_vec()).unwrap())
.collect()
}
}
#[test]
fn help_and_invalid_sources_do_not_build_or_import() {
let fixture = Fixture::new();
let help = fixture.command().arg("--help").output().unwrap();
assert!(help.status.success());
assert!(
String::from_utf8(help.stdout)
.unwrap()
.contains("SOURCE_DIRECTORY")
);
assert!(!fixture.arguments.exists());
for arguments in [vec![], vec![""], vec!["missing source"]] {
let result = fixture.command().args(arguments).output().unwrap();
assert_eq!(result.status.code(), Some(2));
assert!(!fixture.arguments.exists());
}
}
#[test]
fn resolves_source_and_preserves_arguments_and_destination_settings() {
let fixture = Fixture::new();
let source_name = "old app; literal $(no-command)";
let source = fixture.root.join(source_name);
fs::create_dir(&source).unwrap();
let result = fixture
.command()
.args([
source_name,
"--database",
"docker-data/db/original data.db",
"--backup-root",
"/srv/original backups",
])
.env("DATA_DIR", "./new storage")
.env("DATABASE_PATH", "./new storage/custom.db")
.env("CDPATH", &fixture.root)
.output()
.unwrap();
assert!(result.status.success(), "{:?}", result);
assert_eq!(
fixture.arguments(),
[
"import-python",
"--source",
source.canonicalize().unwrap().to_str().unwrap(),
"--database",
"docker-data/db/original data.db",
"--backup-root",
"/srv/original backups",
]
);
assert_eq!(
fs::read(&fixture.environment).unwrap(),
b"./new storage\0./new storage/custom.db\0"
);
}
#[test]
fn forwards_import_failure_without_falling_back_to_serve() {
let fixture = Fixture::new();
fs::create_dir(fixture.root.join("source")).unwrap();
let result = fixture
.command()
.arg("source")
.env("IMPORT_TEST_STATUS", "23")
.output()
.unwrap();
assert_eq!(result.status.code(), Some(23));
assert_eq!(fixture.arguments().len(), 3);
assert_eq!(fixture.arguments()[0], "import-python");
}
+228
View File
@@ -0,0 +1,228 @@
//! Published documentation must match live routes and remain local to this server.
use axum::{
Router,
body::Body,
http::{Request, StatusCode},
};
use http_body_util::BodyExt;
use mikrotik_backup::{AppState, api, config::Config, db::Db};
use serde_json::Value;
use std::collections::{HashMap, HashSet};
use tower::ServiceExt;
fn fixture(prefix: &str) -> (tempfile::TempDir, Router) {
let dir = tempfile::tempdir().unwrap();
let config = Config::from_values(&HashMap::from([
("DATA_DIR".into(), dir.path().display().to_string()),
("DEFAULT_ADMIN_PASSWORD".into(), "docs-test-password".into()),
("API_PREFIX".into(), prefix.into()),
("APP_NAME".into(), "MikroTik API contract test".into()),
]))
.unwrap();
let db = Db::open(&config).unwrap();
(dir, api::router(AppState::new(config, db)))
}
fn check_references(value: &Value, document: &Value) {
match value {
Value::Object(object) => {
if let Some(reference) = object.get("$ref").and_then(Value::as_str) {
assert!(reference.starts_with("#/components/"));
assert!(
document.pointer(&reference[1..]).is_some(),
"Broken ref {reference}"
);
}
for child in object.values() {
check_references(child, document);
}
}
Value::Array(items) => {
for child in items {
check_references(child, document);
}
}
_ => {}
}
}
#[tokio::test]
async fn openapi_covers_live_routes_and_honors_custom_prefix() {
let (_dir, app) = fixture("/v2/api");
let response = app
.clone()
.oneshot(Request::get("/openapi.json").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert_eq!(response.headers()["content-type"], "application/json");
let bytes = response.into_body().collect().await.unwrap().to_bytes();
let document: Value = serde_json::from_slice(&bytes).unwrap();
assert_eq!(document["openapi"], "3.1.0");
assert_eq!(document["info"]["title"], "MikroTik API contract test");
assert_eq!(document["info"]["version"], env!("CARGO_PKG_VERSION"));
assert_eq!(document["servers"][0]["url"], "/v2/api");
assert_eq!(
document["tags"],
serde_json::json!([
{"name": "Account"},
{"name": "Devices"},
{"name": "Backups"},
{"name": "Settings"},
{"name": "Logs"},
{"name": "System"}
])
);
assert!(
document["info"]["description"]
.as_str()
.unwrap()
.starts_with("Documentation for the live API.")
);
assert_eq!(
document["paths"]["/auth/login"]["post"]["summary"],
"Log in"
);
assert_eq!(
document["paths"]["/routers"]["get"]["tags"],
serde_json::json!(["Devices"])
);
assert_eq!(
document["components"]["securitySchemes"]["OAuth2PasswordBearer"]["flows"]["password"]["tokenUrl"],
"/v2/api/auth/login"
);
check_references(&document, &document);
let mut operation_ids = HashSet::new();
let mut operation_count = 0;
for (path, methods) in document["paths"].as_object().unwrap() {
let concrete = ["id", "backup_id", "left", "right", "days"]
.into_iter()
.fold(path.clone(), |path, name| {
path.replace(&format!("{{{name}}}"), "1")
});
for (method, operation) in methods.as_object().unwrap() {
operation_count += 1;
assert!(operation_ids.insert(operation["operationId"].as_str().unwrap()));
let public = ["/health", "/auth/login", "/auth/register"].contains(&path.as_str());
assert_eq!(operation.get("security").is_some(), public);
let response = app
.clone()
.oneshot(
Request::builder()
.method(method.to_ascii_uppercase().as_str())
.uri(format!("/v2/api{concrete}"))
.header("content-type", "application/json")
.body(Body::from("{}"))
.unwrap(),
)
.await
.unwrap();
assert_ne!(
response.status(),
StatusCode::NOT_FOUND,
"Undocumented route mismatch: {method} {path}"
);
assert_ne!(
response.status(),
StatusCode::METHOD_NOT_ALLOWED,
"Undocumented method mismatch: {method} {path}"
);
if !public {
assert_eq!(
response.status(),
StatusCode::UNAUTHORIZED,
"Protected route {method} {path}"
);
}
}
}
assert_eq!(operation_count, 36);
assert_eq!(
document["components"]["schemas"]["Device"]["properties"]["ssh_password"]["type"],
"null"
);
assert!(
document["components"]["schemas"]["DeviceUpdate"]["properties"]
.get("use_global_switchos_credentials")
.is_some()
);
}
#[tokio::test]
async fn docs_and_embedded_assets_work_without_a_cdn() {
let (_dir, app) = fixture("/api");
let mut documentation_html = None;
for path in ["/docs", "/docs/"] {
let response = app
.clone()
.oneshot(Request::get(path).body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(
response.headers()["content-security-policy"]
.to_str()
.unwrap()
.contains("script-src 'self'")
);
let bytes = response.into_body().collect().await.unwrap().to_bytes();
let html = std::str::from_utf8(&bytes).unwrap();
assert!(html.contains("<html lang=\"en\">"));
assert!(html.contains("aria-label=\"API documentation\""));
assert!(html.contains("Back to the application"));
assert!(html.contains(env!("DOCS_JS_ROUTE")));
assert!(html.contains(env!("DOCS_CSS_ROUTE")));
assert!(!html.contains("?v="));
assert_eq!(html.matches("<script ").count(), 1);
assert!(!html.contains("https://"));
if let Some(previous) = &documentation_html {
assert_eq!(html, previous);
} else {
documentation_html = Some(html.to_owned());
}
}
let documentation_html = documentation_html.unwrap();
for (path, mime) in [
(env!("DOCS_JS_ROUTE"), "text/javascript"),
(env!("DOCS_CSS_ROUTE"), "text/css"),
] {
let response = app
.clone()
.oneshot(Request::get(path).body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
assert!(
response.headers()["content-type"]
.to_str()
.unwrap()
.starts_with(mime)
);
assert_eq!(
response.headers()["cache-control"].to_str().unwrap(),
"public, max-age=31536000, immutable"
);
let bytes = response.into_body().collect().await.unwrap().to_bytes();
assert!(bytes.len() > 1000);
assert!(documentation_html.contains(&format!("\"{path}\"")));
if path.ends_with(".js") {
let javascript = std::str::from_utf8(&bytes).unwrap();
assert!(javascript.contains("validatorUrl: null"));
assert!(javascript.contains("queryConfigEnabled: false"));
assert!(javascript.contains("persistAuthorization: false"));
assert!(
javascript.contains("API documentation can send requests only to this instance.")
);
assert!(javascript.ends_with(include_str!("../docs/api-ui/init.js")));
} else {
let stylesheet = std::str::from_utf8(&bytes).unwrap();
assert!(stylesheet.ends_with(include_str!("../docs/api-ui/style.css")));
}
}
let response = app
.oneshot(Request::get("/redoc").body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::PERMANENT_REDIRECT);
assert_eq!(response.headers()["location"], "/docs");
}