feat: define BDD tests per RFC6415

This commit is contained in:
Aravinth Manivannan 2023-10-02 17:44:39 +05:30
parent 76cb235dce
commit 469b002fb4
Signed by: realaravinth
GPG key ID: F8F50389936984FF
3 changed files with 311 additions and 0 deletions

44
features/environment.py Normal file
View file

@ -0,0 +1,44 @@
# SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
from behave import *
from ftest_common.logger import upload_logs_to_ftest
def before_all(context):
context.success = []
context.failure = {}
def after_all(context):
max_score = 9
score = 0
score = len(context.success)
print("\n\n===============")
if score == max_score:
print("All tests passed")
elif score > 0:
print(f"Partial success. {score} out of {max_score} tests passed")
print("Summary:\n")
logs = ""
if context.success:
print(f"Successful tests:\n")
for s in context.success:
log = f"[OK] {s}\n"
print(log)
logs += log
if context.failure:
print(f"\n\nFailed tests:\n")
for _, (test, error) in enumerate(context.failure.items()):
log = f"[FAIL] {test} failed with error:\n{error}\n-----\n"
print(log)
logs += log
upload_logs_to_ftest(score == max_score, logs)

View file

@ -0,0 +1,15 @@
Feature: Host Meta
As defined in https://www.rfc-editor.org/rfc/rfc6415.html
Scenario:
Given A Fediverse server
When Querying /.well-known/host-meta
Then The page must resolve at port 80 or 443
And If both port 80 _and_ 443 work, then they SHOULD return same document
And The document SHOULD be served with "application/xrd+xml" media type
And The host-meta document root MUST be an "XRD" element
And The document SHOULD NOT include a "Subject" element
And The document SHOULD include "Link" element
And The document's "Link" element should include either "template" or "href" attributes
And The document should have at least on 'lrdd' document
And Fediverse specific: The lrdd document must include template containing WebFinger well-known URI

252
features/steps/host_meta.py Normal file
View file

@ -0,0 +1,252 @@
# SPDX-FileCopyrightText: 2023 Aravinth Manivannan <realaravinth@batsense.net>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
import os
import collections.abc
from urllib.parse import urlparse, urlunparse
import requests
import xmltodict
from behave import *
from ftest_common.env import FTEST_USER
from ftest_common.logger import logger
from ftest_common.webfinger import get_webfinger
from ftest_common.obj import get_ap_obj
def query_host_meta(https: bool):
from ftest_common.env import TARGET_HOST
if https:
scheme = "https"
else:
scheme = "http"
parsed_target_host = urlparse(TARGET_HOST)
host_meta = urlunparse(
(
scheme,
parsed_target_host.netloc,
"/.well-known/host-meta",
"",
"",
"",
)
)
logger.debug(f"fetching {host_meta}")
resp = requests.get(host_meta)
logger.debug(
f"host-meta response:\n\nSTATUS: {resp.status_code}\n\nHEADERS:\n {resp.headers}\n\nRESPONSE PAYLOAD:\n{resp.content}"
)
return resp
@given("A Fediverse server")
def step_impl(context):
pass
@when("Querying /.well-known/host-meta")
def step_impl(context):
try:
http_res = query_host_meta(False)
context.http_res = http_res
if http_res.status_code == 200:
context.http_host_meta = xmltodict.parse(http_res.content)
logger.info("[SUCCESS] host-meta available on 80")
context.host_meta = context.http_host_meta
context.resp = context.http_res
except:
logger.debug("[ERROR] host-meta not available on 80")
pass
try:
https_res = query_host_meta(True)
context.https_res = https_res
if https_res.status_code == 200:
context.https_host_meta = xmltodict.parse(https_res.content)
logger.info("[SUCCESS] host-meta available on 443")
context.host_meta = context.https_host_meta
context.resp = context.https_res
except:
logger.debug("[ERROR] host-meta not available on 443")
pass
@then("The page must resolve at port 80 or 443")
def step_impl(context):
name = "Page must resolve at port 80 or 443"
try:
assert (
any(
[
"https_host_meta" in context,
"http_host_meta" in context,
]
)
is True
), name
logger.info("[SUCCESS] {name}")
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then("If both port 80 _and_ 443 work, then they SHOULD return same document")
def step_impl(context):
if all(["https_host_meta" in context, "http_host_meta" in context]):
assert (
context.host_meta == context.https_host_meta
), "Both 80 and 443 returned the same response"
@then('The document SHOULD be served with "application/xrd+xml" media type')
def step_impl(context):
name = 'The document SHOULD be served with "application/xrd+xml" media type'
try:
resps = []
if all(["https_host_meta" in context, "http_host_meta" in context]):
resps = [context.http_res, context.https_res]
else:
resps = [context.resp]
for resp in resps:
assert (
"application/xrd+xml" in resp.headers["Content-Type"]
), "Document served with 'application/xrd+xml' media type"
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then('The host-meta document root MUST be an "XRD" element')
def step_impl(context):
name = 'The host-meta document root MUST be an "XRD" element'
try:
assert len(context.host_meta) == 1, "Only one root element"
assert "XRD" in context.host_meta, "'XRX' is the root element"
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then('The document SHOULD NOT include a "Subject" element')
def step_impl(context):
name = 'The document SHOULD NOT include a "Subject" element'
try:
assert (
"Subject" not in context.host_meta["XRD"]
), "Document SHOULD NOT include a 'Subject' element"
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then('The document SHOULD include "Link" element')
def step_impl(context):
name = 'The document SHOULD include "Link" element'
try:
assert (
"Link" in context.host_meta["XRD"]
), "Document SHOULD include a 'Link' element"
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then(
'The document\'s "Link" element should include either "template" or "href" attributes'
)
def step_impl(context):
def _check(obj):
return any(["@template" in obj, "@href" in obj])
name = 'The document\'s "Link" element should include either "template" or "href" attributes'
try:
if isinstance(context.host_meta["XRD"]["Link"], collections.abc.Sequence):
for link in context.host_meta["XRD"]["Link"]:
assert _check(
link
), f'The document\'s "Link" element should include either "template" or "href" attributes. Got {link}'
else:
link = context.host_meta["XRD"]["Link"]
assert _check(
link
), f'The document\'s "Link" element should include either "template" or "href" attributes. Got {link}'
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then("The document should have at least on 'lrdd' document")
def step_impl(context):
def _check(obj):
return obj["@rel"] == "lrdd"
lrdd_found = False
name = "The document should have at least on 'lrdd' document"
if isinstance(context.host_meta["XRD"]["Link"], collections.abc.Sequence):
for link in context.host_meta["XRD"]["Link"]:
if _check(link):
lrdd_found = True
break
else:
link = context.host_meta["XRD"]["Link"]
if _check(link):
lrdd_found = True
try:
assert lrdd_found is True
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)
@then(
"Fediverse specific: The lrdd document must include template containing WebFinger well-known URI"
)
def step_impl(context):
def _check(obj):
if obj["@rel"] == "lrdd":
if "@template" in obj:
if "/.well-known/webfinger?resource={uri}" in obj["@template"]:
return True
return False
webfinger_found = False
name = "Fediverse specific: The lrdd document must include template containing WebFinger well-known URI"
if isinstance(context.host_meta["XRD"]["Link"], collections.abc.Sequence):
for link in context.host_meta["XRD"]["Link"]:
if _check(link):
webfinger_found = True
break
else:
link = context.host_meta["XRD"]["Link"]
if _check(link):
webfinger_found = True
try:
assert webfinger_found is True
except Exception as e:
logger.error(e)
context.failure[name] = e
raise e
context.success.append(name)