first commit
This commit is contained in:
commit
32a7ba8e8f
168 files changed
+123856
No files matched your search
File diff suppressed because it is too large.
Load diff
Vendored
+162
@@ -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"
|
||||
}
|
||||
]
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Reference in new issue
Block a user