Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add test cases and CI pipeline #3

Merged
merged 18 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: test

on:
push:
branches:
- wip/testing

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
- ubuntu-22.04
- ubuntu-20.04
- windows-latest
- windows-2022
- windows-2019

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
architecture: x64

- name: Install Python dependencies
run: pip install -r requirements.txt

- name: Run Python Tests
run: python -m unittest discover
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# Typical virtualenv dir
/venv/
/.venv/

# IDE settings
/.idea/
Expand Down
2 changes: 1 addition & 1 deletion config.ini.default
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ port=8125
fallback_semantic_matching_service=https://example.org/semantic_matching_service
eclass_semantic_matching_service=https://example.org/semantic_matching_service
cdd_semantic_matching_service=https://example.org/semantic_matching_service
debug_semantic_matching_service_endpoints=../debug_endpoints.json
debug_semantic_matching_service_endpoints=debug_endpoints.json
Binary file modified requirements.txt
moritzsommer marked this conversation as resolved.
Show resolved Hide resolved
Binary file not shown.
29 changes: 18 additions & 11 deletions semantic_id_resolver/resolver.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import enum
from pathlib import Path
from typing import Optional, Dict
from urllib.parse import urlparse
import json

import dns.resolver
from parser.irdi_parser import IRDIParser


class DebugSemanticMatchingServiceEndpoints:
Expand All @@ -23,7 +25,9 @@ def __init__(self, debug_endpoints: Dict[str, str]):

@classmethod
def from_file(cls, filename: str) -> "DebugSemanticMatchingServiceEndpoints":
with open(filename, "r") as file:
base_dir = str(Path(__file__).resolve().parent.parent)
resource_path = base_dir + "/" + filename
with open(resource_path, "r") as file:
debug_endpoints = json.load(file)
return DebugSemanticMatchingServiceEndpoints(debug_endpoints)

Expand All @@ -33,18 +37,20 @@ def get_debug_endpoint(self, semantic_id: str) -> Optional[str]:

def is_iri_not_irdi(semantic_id: str) -> Optional[bool]:
"""
:return: `True`, if `semantic_id` is a IRI, False if it is an `IRDI`, None for neither
:return: `True`, if `semantic_id` is an IRI, False if it is an IRDI, None for neither
"""
# Check IRDI
try:
IRDIParser().parse(semantic_id)
return False
except ValueError:
pass
# Check IRI
parsed_url = urlparse(semantic_id)
# Check if the scheme is present, which indicates it's a URI
if parsed_url.scheme:
return True
# Check if there is a colon in the netloc, which could indicate an IRI
elif ':' in parsed_url.netloc:
return False
# If neither condition is met, return None
else:
return None
# Not IRDI or IRI
return None


class IRDISources(enum.Enum):
Expand Down Expand Up @@ -103,9 +109,10 @@ def find_semantic_matching_service(self, semantic_id: str) -> Optional[str]:
return debug_endpoint

# Check for IRI and IRDI
if is_iri_not_irdi(semantic_id) is True:
is_iri = is_iri_not_irdi(semantic_id)
if is_iri is True:
return _iri_find_semantic_matching_service(semantic_id)
elif is_iri_not_irdi(semantic_id) is False:
elif is_iri is False:
return self._irdi_find_semantic_matching_service(semantic_id)
else:
return None
Expand Down
4 changes: 2 additions & 2 deletions semantic_id_resolver/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def get_semantic_matching_service(
endpoint = found_endpoint
return SMSResponse(
semantic_matching_service_endpoint=endpoint,
meta_information={} # Todo
meta_information={}
)


Expand All @@ -82,7 +82,7 @@ def get_semantic_matching_service(
DEBUG_ENDPOINTS = resolver.DebugSemanticMatchingServiceEndpoints.from_file(
config["RESOLVER"]["debug_semantic_matching_service_endpoints"]
)
print(f"USING DEBUG ENDPOINTS FROM {config["RESOLVER"]["debug_semantic_matching_service_endpoints"]}")
print(f"USING DEBUG ENDPOINTS FROM {config['RESOLVER']['debug_semantic_matching_service_endpoints']}")
except FileNotFoundError:
DEBUG_ENDPOINTS = resolver.DebugSemanticMatchingServiceEndpoints(debug_endpoints={})

Expand Down
Empty file added test/__init__.py
Empty file.
145 changes: 145 additions & 0 deletions test/test_resolving_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import os
import configparser
from typing import Dict
import multiprocessing

import dns
import requests
import unittest

from fastapi import FastAPI
import uvicorn

from semantic_id_resolver import resolver
from semantic_id_resolver.service import SemanticIdResolvingService, SMSRequest

from contextlib import contextmanager
import signal
import time


def run_server():
# Load test configuration
config = configparser.ConfigParser()
config.read([
os.path.abspath(os.path.join(os.path.dirname(__file__), "../test_resources/config.ini")),
])

# Define test configuration
IRDI_MATCHER_DICT: Dict[resolver.IRDISources, str] = {
resolver.IRDISources.ECLASS: config["RESOLVER"]["eclass_semantic_matching_service"],
resolver.IRDISources.IEC_CDD: config["RESOLVER"]["cdd_semantic_matching_service"]
}

try:
DEBUG_ENDPOINTS = resolver.DebugSemanticMatchingServiceEndpoints.from_file(
config["RESOLVER"]["debug_semantic_matching_service_endpoints"]
)
print(f"USING DEBUG ENDPOINTS FROM {config['RESOLVER']['debug_semantic_matching_service_endpoints']}")
except FileNotFoundError:
DEBUG_ENDPOINTS = resolver.DebugSemanticMatchingServiceEndpoints(debug_endpoints={})

# Mock SemanticIdResolvingService for testing
mock_resolver = resolver.SemanticIdResolver(IRDI_MATCHER_DICT, DEBUG_ENDPOINTS)
semantic_id_resolver_service = SemanticIdResolvingService(
endpoint=config["SERVICE"]["endpoint"],
fallback_semantic_matching_service_endpoint=config["RESOLVER"]["fallback_semantic_matching_service"],
semantic_id_resolver=mock_resolver
)

# Mock TXT record
class MockTXTRecord:
def __init__(self, strings):
self.strings = strings

# Mock DNS resolving manually, mock for unittest does not work in this context
def mock_dns_resolver_resolve(qname, rdtype):
return [MockTXTRecord([b"semantic_matcher: https://example.org/iri_semantic_matching_service"])]
dns.resolver.resolve = mock_dns_resolver_resolve

# Run server
app = FastAPI()
app.include_router(semantic_id_resolver_service.router)
uvicorn.run(app, host=str(config["SERVICE"]["ENDPOINT"]), port=int(config["SERVICE"]["PORT"]), log_level="error")


@contextmanager
def run_server_context():
server_process = multiprocessing.Process(target=run_server)
server_process.start()
try:
time.sleep(2) # Wait for the server to start
yield
finally:
server_process.terminate()
server_process.join(timeout=5)
if server_process.is_alive():
os.kill(server_process.pid, signal.SIGKILL)
server_process.join()


class TestSemanticMatchingService(unittest.TestCase):

def test_semantic_matching_service_iri(self):
with run_server_context():
sms_request = SMSRequest(semantic_id="foo://example.org:1234/over/there?name=bar#page=3")
response = requests.get(
"http://localhost:8125/get_semantic_matching_service",
data=sms_request.model_dump_json()
)
self.assertEqual(
"https://example.org/iri_semantic_matching_service",
response.json()["semantic_matching_service_endpoint"]
)

def test_semantic_matching_service_irdi_eclass(self):
with run_server_context():
sms_request = SMSRequest(semantic_id="0173-0001#01-ACK323#7")
response = requests.get(
"http://localhost:8125/get_semantic_matching_service",
data=sms_request.model_dump_json()
)
self.assertEqual(
"https://example.org/eclass_semantic_matching_service",
response.json()["semantic_matching_service_endpoint"]
)

def test_semantic_matching_service_irdi_cdd(self):
with run_server_context():
sms_request = SMSRequest(semantic_id="0112-0001#01-ACK323#7")
response = requests.get(
"http://localhost:8125/get_semantic_matching_service",
data=sms_request.model_dump_json()
)
self.assertEqual(
"https://example.org/cdd_semantic_matching_service",
response.json()["semantic_matching_service_endpoint"]
)

def test_semantic_matching_service_fallback(self):
with run_server_context():
sms_request = SMSRequest(semantic_id="nothing")
response = requests.get(
"http://localhost:8125/get_semantic_matching_service",
data=sms_request.model_dump_json()
)
self.assertEqual(
"https://example.org/fallback_semantic_matching_service",
response.json()["semantic_matching_service_endpoint"]
)

def test_semantic_matching_service_debug(self):
with run_server_context():
sms_request = SMSRequest(semantic_id="https://example.org/semanticIDone")
response = requests.get(
"http://localhost:8125/get_semantic_matching_service",
data=sms_request.model_dump_json()
)
self.assertEqual(
"https://example.org/debug_semantic_matching_service",
response.json()["semantic_matching_service_endpoint"]
)


if __name__ == '__main__':
unittest.main()
9 changes: 9 additions & 0 deletions test_resources/config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[SERVICE]
endpoint=127.0.0.1
port=8125

[RESOLVER]
fallback_semantic_matching_service=https://example.org/fallback_semantic_matching_service
eclass_semantic_matching_service=https://example.org/eclass_semantic_matching_service
cdd_semantic_matching_service=https://example.org/cdd_semantic_matching_service
debug_semantic_matching_service_endpoints=test_resources/debug_endpoints.json
3 changes: 3 additions & 0 deletions test_resources/debug_endpoints.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"https://example.org/semanticIDone": "https://example.org/debug_semantic_matching_service"
}
Loading