summaryrefslogtreecommitdiffstats
path: root/modules/http/test_tls
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-08 20:37:50 +0000
commitc1f743ab2e4a7046d5500875a47d1f62c8624603 (patch)
tree709946d52f5f3bbaeb38be9e3f1d56d11f058237 /modules/http/test_tls
parentInitial commit. (diff)
downloadknot-resolver-913a96f00351e791fb56b1cd60642cb5fc7f7683.tar.xz
knot-resolver-913a96f00351e791fb56b1cd60642cb5fc7f7683.zip
Adding upstream version 5.7.1.upstream/5.7.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/http/test_tls')
-rw-r--r--modules/http/test_tls/broken.crt3
-rw-r--r--modules/http/test_tls/broken.keybin0 -> 512 bytes
-rw-r--r--modules/http/test_tls/ca.crt20
-rw-r--r--modules/http/test_tls/chain.crt41
-rw-r--r--modules/http/test_tls/test.crt20
-rw-r--r--modules/http/test_tls/test.key27
-rw-r--r--modules/http/test_tls/tls.test.lua193
7 files changed, 304 insertions, 0 deletions
diff --git a/modules/http/test_tls/broken.crt b/modules/http/test_tls/broken.crt
new file mode 100644
index 0000000..d93d1f8
--- /dev/null
+++ b/modules/http/test_tls/broken.crt
@@ -0,0 +1,3 @@
+ƿps$֡ȼ[1 =fl:l=z=M}iɻѭ*7) 5
+jIW Mwf[H-Eȼf   2fTKqFU (ja,՜*X:lFͿM>3
+D<^OqkQκMg]pUNMݝ>(EI'Gŀm:3 _!Β? 3$H[EM4RA+0w0%eoa(w;oǥ$!Zr%&h;1@-9((b7\UoJ`:ު~dÎaЃEœPB*l}!q7;+QRLvQ[KYXR 2(7+$E,IR ^4D_r,i\hξ \ No newline at end of file
diff --git a/modules/http/test_tls/broken.key b/modules/http/test_tls/broken.key
new file mode 100644
index 0000000..ebcbfcf
--- /dev/null
+++ b/modules/http/test_tls/broken.key
Binary files differ
diff --git a/modules/http/test_tls/ca.crt b/modules/http/test_tls/ca.crt
new file mode 100644
index 0000000..81a42d2
--- /dev/null
+++ b/modules/http/test_tls/ca.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVzCCAj+gAwIBAgIUAu0lPjQwQtB0QMMjaFfa+RrJPo0wDQYJKoZIhvcNAQEL
+BQAwMzELMAkGA1UEBhMCR0IxEDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFs
+aWNlIEx0ZDAeFw0xOTAzMjgxMjUzMjNaFw0zOTAzMjMxMjUzMjNaMDMxCzAJBgNV
+BAYTAkdCMRAwDgYDVQQIDAdFbmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQwggEi
+MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkLE9N62uDhi/zecTnGgQisDcg
+RuSgvU8Z/CVQC5WvSmZxO/RKw+TwSKigVMBiLKvi2gZwda4QZN1+If2/IPO41NvV
+Gc9vMffOpYCi3dLW9u3a3n7ZDHUlbmbYMyrkWcYEKC+wGBgvg9a1bce3VEvLGj/t
+YtTV4DPiUQz87jQxRp1a++XVvYkM6XpRIxfoyRdjW7ixssdpkd3DAsf2+qS35Ax8
+XTUvcoHuBcL2j4FAvptRk09T4S7bYLzx5cG0DBxhc75++n1T02D40D5uV7j2OI2N
+pQpsGhewrvS+9+F7z2f7Kpc1slJ7MW5eiYflF/R4gDxFTd0kZ9YWeRwwf1ltAgMB
+AAGjYzBhMB0GA1UdDgQWBBQRVncFgsUWq566JMAcejJj0t9uFjAfBgNVHSMEGDAW
+gBQRVncFgsUWq566JMAcejJj0t9uFjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEANdM7o2phBhK5ZhRVjQQb7Ff+l1zn
+EZ5HVnn+YwL3y9h8nJsW5Y1xiIm1w/Apn+gqrBYUMchJxQ1/ha0Bza6kl29i2fck
+oAAaduTsZImiKCAMxlN8KpJm9DZ3woCwktG7orgzEFgVOiAqk6wbXaff1rAgoVDV
+nzWKh7AoyGkXxFI+wrJFODl92a+gy20pNZCDQqVtiC8AiHl2p+UrU8f7yzjyUyyl
+4ZKyvVne+SB4n4mug1JRLgxTM1C/Qp21DaYaCAYeqT7MZTd7d5nm0nWNOSlZ2lrZ
+e3RuQZic89G1OK9qvjfaUMvzm0iAE9hZ7d3ac1PU+KqRmi0TTO1y62M+rA==
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/chain.crt b/modules/http/test_tls/chain.crt
new file mode 100644
index 0000000..1822e6d
--- /dev/null
+++ b/modules/http/test_tls/chain.crt
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDEYMBYGA1UEAwwP
+SW50ZXJtZWRpYXRlIENBMB4XDTE5MDYxNDA5NTAzOVoXDTM5MDYwOTA5NTAzOVow
+WDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
+dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIa3IubG9jYWwwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVh57hcYaSjQVQ+cUPD++o4ws0vI+S
+2LGQ4u+YzRvx2o3r85DFklfYTM2OICJKEyMNsUm6c45gc5Gq1DBXbmQ+bUTL+Be5
+olJYO1IVP5CcTUmqUOw6yqm4idqoDdmRxwhglRIqv4OYOcU6JBlOqQHmYIKTiwgU
+I0y0lRiWQF+lPaa97HI0YrTcqYG0eLMCDKWnk1FBlIvOcrnrOS7/zzcYYoytYvNi
+//iavyQNMfHlhPUNNmE3TNQ6OmRkKI8wvoRkrvjfnwf04suIrOkd1ADdNqguWTUO
+pNE316lgmZpkfd4hYzlSGTCfjuhRqa1/a7eA8GNkKq03xuOI75rR15eZAgMBAAGj
+NTAzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBkGA1UdEQQSMBCCCGtyLmxvY2Fs
+hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBC4Op5SaXqAdvZKjqO8cd8JpJlnGa/
+UnuQAsVgsQ2giBuZMIMIHzxawbEfCOHhkv1doLW9uEWyPQEGdQntoCHqc4Mb70Zd
+XBcepTlqUprpx/k6nw2io3JCUMmhbgfme63c5OiodxbBQCfWf6vIZjfOSPN0IYVr
+NY4kc0jcUEN0dsqjXEqKVbiDUcApKGGbe87KmSqi02UZHSoBPxm/Tr12n1Eu4AJR
+k7e3tJ3dslIYxYbQE6tUKiPWlP+rETcL7ran0m3YzEplMPvZdme9mu3w9Upsigx7
+lRCELEjwpBPZ5cUD04SkesoTGP6g8ZbmdpyjkI7lOjXvrBDim20EK47x
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAkqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwMzELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDAeFw0xOTAzMjgx
+MjU5NTRaFw0zOTAzMjMxMjU5NTRaME0xCzAJBgNVBAYTAkdCMRAwDgYDVQQIDAdF
+bmdsYW5kMRIwEAYDVQQKDAlBbGljZSBMdGQxGDAWBgNVBAMMD0ludGVybWVkaWF0
+ZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMOH4k6f8wL5IruB
+m1BqQCE4ZQnDN8SX1ZwDp7G7tlycnJDB9PBkCziZcU11ezw6xdhT3J0jCEDwLrx2
+gsi+DWJwsC4CfcLdN1iuPeMRZQyC4GmQzPTJmQ8Kl/dQFZPV0wkd+JZNOfvpxpnD
+5WHkqg3DX6933/86raZ3NVfeVmTdHRdol+K8ve+1924IfrZNbZxatCX7AwEgyUFi
+qogXjcO+fqQed8VzHXGasqYMMLlMZMGLmusHlK6pDyscayubNOk1U9eHoRNYp6cb
+LChG8WnSpFv/LAB6QuyxvOwTK86R7k3J48ProPEFHkhUhHXxIHnR5l5+XLxcQSJ9
+xP7dFL8CAwEAAaNmMGQwHQYDVR0OBBYEFOaglkXoUdbRnwAXnDbbX5g62VXQMB8G
+A1UdIwQYMBaAFBFWdwWCxRarnrokwBx6MmPS324WMBIGA1UdEwEB/wQIMAYBAf8C
+AQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IBAQDeEA0klOFmdxHV
+jO0z2nXKE2jRvckoSKDGxj0L6gSxoUVRqVJMvlYmje8i0D9NyFCTlhRBX/24NCoZ
+2Oe4drzAiF+g97Ns7wIyYhAhcdJLn/820FZLsDMUbvW0BSsKsFckEABev3/miuJE
+O5/OzkU53V+7Nu4zT8CcI5cni2tn7Xq0p0L410lL6Lobkn7jON+BNhshK+H/Vcwj
+hf8L+ZxlDomXx7SfcIDc6AdFz2t4gu+6CyP/G/zeqUOPeh2WrqHg+caPQcq58Ol8
+p1r258mH8fxafu7Qcm2j23ZjW7xrDVC2XLWeXozJztlnODv73Jm686jHKflcfFTm
+6OgxKoIT
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/test.crt b/modules/http/test_tls/test.crt
new file mode 100644
index 0000000..e9405f9
--- /dev/null
+++ b/modules/http/test_tls/test.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDVjCCAj6gAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwTTELMAkGA1UEBhMCR0Ix
+EDAOBgNVBAgMB0VuZ2xhbmQxEjAQBgNVBAoMCUFsaWNlIEx0ZDEYMBYGA1UEAwwP
+SW50ZXJtZWRpYXRlIENBMB4XDTE5MDYxNDA5NTAzOVoXDTM5MDYwOTA5NTAzOVow
+WDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu
+dGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UEAwwIa3IubG9jYWwwggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVh57hcYaSjQVQ+cUPD++o4ws0vI+S
+2LGQ4u+YzRvx2o3r85DFklfYTM2OICJKEyMNsUm6c45gc5Gq1DBXbmQ+bUTL+Be5
+olJYO1IVP5CcTUmqUOw6yqm4idqoDdmRxwhglRIqv4OYOcU6JBlOqQHmYIKTiwgU
+I0y0lRiWQF+lPaa97HI0YrTcqYG0eLMCDKWnk1FBlIvOcrnrOS7/zzcYYoytYvNi
+//iavyQNMfHlhPUNNmE3TNQ6OmRkKI8wvoRkrvjfnwf04suIrOkd1ADdNqguWTUO
+pNE316lgmZpkfd4hYzlSGTCfjuhRqa1/a7eA8GNkKq03xuOI75rR15eZAgMBAAGj
+NTAzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMBkGA1UdEQQSMBCCCGtyLmxvY2Fs
+hwR/AAABMA0GCSqGSIb3DQEBCwUAA4IBAQBC4Op5SaXqAdvZKjqO8cd8JpJlnGa/
+UnuQAsVgsQ2giBuZMIMIHzxawbEfCOHhkv1doLW9uEWyPQEGdQntoCHqc4Mb70Zd
+XBcepTlqUprpx/k6nw2io3JCUMmhbgfme63c5OiodxbBQCfWf6vIZjfOSPN0IYVr
+NY4kc0jcUEN0dsqjXEqKVbiDUcApKGGbe87KmSqi02UZHSoBPxm/Tr12n1Eu4AJR
+k7e3tJ3dslIYxYbQE6tUKiPWlP+rETcL7ran0m3YzEplMPvZdme9mu3w9Upsigx7
+lRCELEjwpBPZ5cUD04SkesoTGP6g8ZbmdpyjkI7lOjXvrBDim20EK47x
+-----END CERTIFICATE-----
diff --git a/modules/http/test_tls/test.key b/modules/http/test_tls/test.key
new file mode 100644
index 0000000..1faa097
--- /dev/null
+++ b/modules/http/test_tls/test.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEA1Yee4XGGko0FUPnFDw/vqOMLNLyPktixkOLvmM0b8dqN6/OQ
+xZJX2EzNjiAiShMjDbFJunOOYHORqtQwV25kPm1Ey/gXuaJSWDtSFT+QnE1JqlDs
+OsqpuInaqA3ZkccIYJUSKr+DmDnFOiQZTqkB5mCCk4sIFCNMtJUYlkBfpT2mvexy
+NGK03KmBtHizAgylp5NRQZSLznK56zku/883GGKMrWLzYv/4mr8kDTHx5YT1DTZh
+N0zUOjpkZCiPML6EZK74358H9OLLiKzpHdQA3TaoLlk1DqTRN9epYJmaZH3eIWM5
+Uhkwn47oUamtf2u3gPBjZCqtN8bjiO+a0deXmQIDAQABAoIBAQCBnoUk50xAlBhh
+Em27+fmKtOBtj/U7uAz6HbhCMmg/RWOXktAUDwUCSYUSPJF0E+/YdQGDjHgmNqF7
+aLk7qchyWNRFWQHV7yI7ay8ltONs7kHEgMEV40Zpvk0cbOPg6Ug9kOBpUL5qXs9J
+vvYZ2OBNX9KEDAbIarE6gbNeKg+ldw5q8kCpDWpv+N7zmXq/9GDWPNBG/5lvGYrV
+MgBm6X4OdaYBD234MV5fNLtqiZQuM3JquEf4gFCCeBpA1MJYcodC+VcXNeZotziZ
+xssbGVsevPpLUkFNDV0s5UFYzEKgoCPNS+usNnwD5r6rFof7vEt8UHfKI2Dz6C9R
+q9xMFt4BAoGBAOzdsCQbveuGbae1HD616TliwgrGjaOVh8sT3r79YRLwekhWgqaP
+vbH0m6sEHKi0Sltj8jWd/ktO0ttfaMy45/DQRNjtPOwhr4kT+bmuyrMCfVOWsW/L
+bRrCU348OKr+VyhEI4YEFsaN1jpiwaQmaqcZFbGKJM84wW1OccUY2d7BAoGBAObH
+WPMKU1YwJmeMs38/MMUwWaw7BrLLk2zRsrOLX0ongp4+TGgI0iaFr8gwh3gAUc6W
+VjPyQtlWh2PLA9VabnSAmPu/NNq7X8fbpKFaBFdlkcFq7DpA8RuBN/b6+p4LD2Tb
+2q92VF4szrcflCnXnZrNxUqjY7NPxARw3y6vgUbZAoGAKImIK6XTywsmmR0VyGW5
+lGiibNWuR+C/bLHp3SXgBy3Av8COe5L+FAaY3ZvGi9jPIPTp7uMrMhg7Xe/mL6M1
+jrEWF0oCsybQs9UHWA/iAODcMgIIO+nEsl+vilskF5+PqwR+T+FDRJfhofxkx4ML
+na1dWRUbV5uO/vX94o1uPAECgYBTwHTffxfPZ5oIal+aBmzEo09n2eQMbyUJkPCx
+iBsE5mHY2/MOrmTV5h5tIG+JdVQ7DQQrxffMuEJaTQsPGsqLLUBX3IRp/SY9edC9
+XdXFge7rqsogOgFGYhbVYzAguxLTH5a1ptPneYtrmeJDbSSdUaAP/kvof0I7+lqE
+rtzTwQKBgQDTlchGtbuVAQzWuaN86UyXU43Ug9YybyLhanh8ZLCoUxsZVf2D2zVb
+TvqzKCx2qP+f3c6ydkg7pRSY0R80bxshhv1qUAvEqC74JgLBDlD7R/rQlj17MtNl
+G3TZIcskdnaHY5zydtvxXJxNxCCHJyBYqWN4L6NzJ+hp7lbP6hQICQ==
+-----END RSA PRIVATE KEY-----
diff --git a/modules/http/test_tls/tls.test.lua b/modules/http/test_tls/tls.test.lua
new file mode 100644
index 0000000..7d5c437
--- /dev/null
+++ b/modules/http/test_tls/tls.test.lua
@@ -0,0 +1,193 @@
+-- SPDX-License-Identifier: GPL-3.0-or-later
+-- check prerequisites
+local has_http = pcall(require, 'kres_modules.http') and pcall(require, 'http.request')
+if not has_http then
+ -- skipping http module test because its not installed
+ os.exit(77)
+else
+ local request = require('http.request')
+ local openssl_ctx = require('openssl.ssl.context')
+
+ local function setup_module(desc, config)
+ if http then
+ modules.unload('http')
+ end
+ modules.load('http')
+ same(http.config(config), nil, desc .. ' can be configured')
+
+ local bound
+ for _ = 1,1000 do
+ bound, _err = pcall(net.listen, '127.0.0.1', math.random(1025,65535), { kind = 'webmgmt' })
+ if bound then
+ break
+ end
+ end
+ assert(bound, 'unable to bind a port for HTTP module (1000 attempts)')
+
+ local server_fd = next(http.servers)
+ assert(server_fd)
+ local server = http.servers[server_fd].server
+ ok(server ~= nil, 'creates server instance')
+ _, host, port = server:localname()
+ ok(host and port, 'binds to an interface')
+ return host, port
+ end
+
+ local function http_get(uri)
+ -- disable certificate verification in this test
+ local req = request.new_from_uri(uri)
+ local idxstart = string.find(uri, 'https://')
+ if idxstart == 1 then
+ req.ctx = openssl_ctx.new()
+ assert(req.ctx, 'OpenSSL cert verification must be disabled')
+ req.ctx:setVerify(openssl_ctx.VERIFY_NONE)
+ end
+
+ local headers = assert(req:go(16))
+ return tonumber(headers:get(':status'))
+ end
+
+ -- test whether http interface responds and binds
+ local function check_protocol(uri, description, ok_expected)
+ if ok_expected then
+ local code = http_get(uri)
+ same(code, 200, description)
+ else
+ boom(http_get, {uri}, description)
+ end
+ end
+
+ local function test_defaults()
+ local host, port = setup_module('HTTP module default config', nil)
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP is enabled by default', true)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS is enabled by default', true)
+
+ modules.unload('http')
+ uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP stops working after module unload', false)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS stops working after module unload', false)
+
+ end
+
+ local function test_http_only()
+ local desc = 'HTTP-only config'
+ local host, port = setup_module(desc,
+ {
+ tls = false,
+ })
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP works in ' .. desc, true)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS does not work in ' .. desc, false)
+ end
+
+ local function test_https_only()
+ local desc = 'HTTPS-only config'
+ local host, port = setup_module(desc,
+ {
+ tls = true,
+ })
+
+ local uri = string.format('http://%s:%d', host, port)
+ check_protocol(uri, 'HTTP does not work in ' .. desc, false)
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS works in ' .. desc, true)
+ end
+
+ local function test_custom_cert()
+ desc = 'config with custom certificate'
+ local host, port = setup_module(desc, {{
+ cert = 'test.crt',
+ key = 'test.key'
+ }})
+
+ uri = string.format('https://%s:%d', host, port)
+ check_protocol(uri, 'HTTPS works for ' .. desc, true)
+ end
+
+ local function test_nonexistent_cert()
+ desc = 'config with non-existing certificate file'
+ boom(http.config, {{
+ cert = '/tmp/surely_nonexistent_cert_1532432095',
+ key = 'test.key'
+ }}, desc)
+ end
+
+ local function test_nonexistent_key()
+ desc = 'config with non-existing key file'
+ boom(http.config, {{
+ cert = 'test.crt',
+ key = '/tmp/surely_nonexistent_cert_1532432095'
+ }}, desc)
+ end
+
+ local function test_missing_key_param()
+ desc = 'config with missing key= param'
+ boom(http.config, {{
+ cert = 'test.crt'
+ }}, desc)
+ end
+
+ local function test_broken_cert()
+ desc = 'config with broken file in cert= param'
+ boom(http.config, {{
+ cert = 'broken.crt',
+ key = 'test.key'
+ }}, desc)
+ end
+
+ local function test_broken_key()
+ desc = 'config with broken file in key= param'
+ boom(http.config, {{
+ cert = 'test.crt',
+ key = 'broken.key'
+ }}, desc)
+ end
+
+ local function test_certificate_chain()
+ local desc = 'config with certificate chain (with intermediate CA cert)'
+ local host, port = setup_module(desc,
+ {
+ tls = true,
+ cert = 'chain.crt',
+ key = 'test.key',
+ })
+ local uri = string.format('https://%s:%d', host, port)
+ local req = request.new_from_uri(uri)
+ req.ctx = openssl_ctx.new()
+
+ if not req.ctx.setCertificateChain then
+ pass(string.format('SKIP (luaossl <= 20181207) - %s', desc))
+ else
+ local store = req.ctx:getStore()
+ store:add('ca.crt')
+ req.ctx:setVerify(openssl_ctx.VERIFY_PEER)
+
+ local headers = assert(req:go(16))
+ local code = tonumber(headers:get(':status'))
+ same(code, 200, desc)
+ end
+ end
+
+
+ -- plan tests
+ local tests = {
+ test_defaults,
+ test_http_only,
+ test_https_only,
+ test_custom_cert,
+ test_nonexistent_cert,
+ test_nonexistent_key,
+ test_missing_key_param,
+ test_broken_cert,
+ test_broken_key,
+ test_certificate_chain,
+ }
+
+ return tests
+end