Skip to content
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
example/
*.jsonld
*.sig
*.tsr

# Byte-compiled / optimized / DLL files
tro_utils/_version.py
__pycache__/
*.py[cod]
*$py.class
Expand Down
100 changes: 100 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""Shared fixtures for tro_utils tests."""

import json

import gnupg
import pytest


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def temp_workspace(tmp_path):
"""Create a temporary workspace with sample files."""
workspace = tmp_path / "workspace"
workspace.mkdir()

# Create sample CSV file with mock data
csv_file = workspace / "input_data.csv"
csv_file.write_text("name,value,category\nitem1,100,A\nitem2,200,B\nitem3,150,A\n")

# Create a text file with some initial content
txt_file = workspace / "notes.txt"
txt_file.write_text("Initial notes\n")

# Create a config file
config_file = workspace / "config.json"
config_file.write_text('{"threshold": 150, "mode": "filter"}')

return workspace


@pytest.fixture(scope="session")
def gpg_setup(tmp_path_factory):
"""Set up a temporary GPG environment for testing (once per session)."""
gpg_home = tmp_path_factory.mktemp("gnupg")
gpg_home.chmod(0o700)

# Create GPG instance
gpg = gnupg.GPG(gnupghome=str(gpg_home))

# Generate a test key (this is slow, so we only do it once)
input_data = gpg.gen_key_input(
key_type="RSA",
key_length=2048,
name_real="Test User",
name_email="test@example.com",
passphrase="test_passphrase",
)
key = gpg.gen_key(input_data)

# Get the key fingerprint and keyid
# Note: gnupg library doesn't populate key_map on initial generation
# We need to use the key result directly
keys = gpg.list_keys()
fingerprint = str(key) # The gen_key returns the fingerprint

# Find the keyid from the keys list
keyid = None
for k in keys:
if k["fingerprint"] == fingerprint:
keyid = k["keyid"]
break

return {
"gpg_home": str(gpg_home),
"gpg": gpg,
"fingerprint": fingerprint,
"keyid": keyid,
"passphrase": "test_passphrase",
}


@pytest.fixture(scope="session")
def trs_profile(tmp_path_factory):
"""Create a TRS profile file (once per session)."""
profile_dir = tmp_path_factory.mktemp("profiles")
profile_file = profile_dir / "trs.jsonld"
profile_data = {
"rdfs:comment": "Test TRS for testing purposes",
"trov:hasCapability": [
{"@id": "trs/capability/1", "@type": "trov:CanRecordInternetAccess"},
{"@id": "trs/capability/2", "@type": "trov:CanProvideInternetIsolation"},
],
"trov:owner": "Test User",
"trov:description": "Test TRS",
"trov:contact": "test@example.com",
"trov:url": "http://localhost/",
"trov:name": "test-trs",
}
profile_file.write_text(json.dumps(profile_data, indent=2))
return str(profile_file)


@pytest.fixture
def tro_declaration(tmp_path):
"""Return path for TRO declaration file."""
return str(tmp_path / "test_tro.jsonld")
29 changes: 29 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Shared helper functions for tro_utils tests."""

import os

from tro_utils.tro_utils import TRO


def create_tro_with_gpg(filepath, gpg_setup, **kwargs):
"""Helper to create TRO with proper GPG configuration."""
# Set GPG_HOME environment variable
os.environ["GPG_HOME"] = gpg_setup["gpg_home"]

# Temporarily remove gpg_fingerprint from kwargs to avoid key_map lookup error
gpg_fingerprint = kwargs.pop("gpg_fingerprint", None)

# Create TRO instance without fingerprint first
tro = TRO(filepath=filepath, **kwargs)

# Now manually set up the GPG key if fingerprint was provided
# This works around the key_map issue in the gnupg library
if gpg_fingerprint:
tro.gpg = gpg_setup["gpg"]
tro.gpg_key_id = gpg_setup["keyid"]
tro.data["@graph"][0]["trov:wasAssembledBy"]["trov:publicKey"] = (
tro.gpg.export_keys(tro.gpg_key_id)
)
tro.gpg_passphrase = kwargs.get("gpg_passphrase")

return tro
93 changes: 93 additions & 0 deletions tests/test_extra_context.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
"""Tests for extra vocabulary support in @context."""

import json

import pytest

from tro_utils.models import TransparentResearchObject
from tro_utils.tro_utils import TRO


class TestExtraContext:
"""Tests for extra vocabulary support in @context."""

def test_extra_context_default_empty(self):
"""TransparentResearchObject.extra_context defaults to an empty list."""
tro = TransparentResearchObject()
assert tro.extra_context == []

def test_extra_context_dict_in_jsonld(self):
"""A dict extra_context entry appears in the serialised @context."""
tro = TransparentResearchObject()
tro.extra_context = [{"ex": "http://example.org/"}]
data = tro.to_jsonld()
assert {"ex": "http://example.org/"} in data["@context"]

def test_extra_context_multiple_entries(self):
"""Multiple extra_context dict entries all appear in @context."""
tro = TransparentResearchObject()
tro.extra_context = [
{"ex": "http://example.org/"},
{"foaf": "http://xmlns.com/foaf/0.1/"},
]
data = tro.to_jsonld()
assert {"ex": "http://example.org/"} in data["@context"]
assert {"foaf": "http://xmlns.com/foaf/0.1/"} in data["@context"]

def test_extra_context_base_context_still_present(self):
"""The standard base context is always the first entry in @context."""
tro = TransparentResearchObject()
tro.extra_context = [{"ex": "http://example.org/"}]
data = tro.to_jsonld()
base = data["@context"][0]
assert "trov" in base
assert "rdf" in base

def test_extra_context_roundtrip(self, tmp_path):
"""Extra context dict entries survive a save/load roundtrip."""
tro = TransparentResearchObject()
tro.extra_context = [
{"ex": "http://example.org/"},
{"foaf": "http://xmlns.com/foaf/0.1/"},
]
filepath = tmp_path / "tro_with_extra_ctx.jsonld"
tro.save(str(filepath))

loaded = TransparentResearchObject.load(str(filepath))
assert {"ex": "http://example.org/"} in loaded.extra_context
assert {"foaf": "http://xmlns.com/foaf/0.1/"} in loaded.extra_context

def test_extra_context_via_tro_facade(self, tmp_path):
"""TRO facade propagates extra_context to the underlying model."""
tro_file = tmp_path / "tro.jsonld"
tro = TRO(
filepath=str(tro_file),
extra_context=[{"ex": "http://example.org/"}],
)
tro.save()

with open(tro_file) as f:
data = json.load(f)
assert {"ex": "http://example.org/"} in data["@context"]

def test_extra_context_via_tro_facade_extends_existing(self, tmp_path):
"""TRO facade appends extra_context to any context already in the file."""
tro_file = tmp_path / "tro.jsonld"
# First save with one extra context entry
tro = TRO(
filepath=str(tro_file),
extra_context=[{"ex": "http://example.org/"}],
)
tro.save()

# Re-open and add a second entry
tro2 = TRO(
filepath=str(tro_file),
extra_context=[{"foaf": "http://xmlns.com/foaf/0.1/"}],
)
tro2.save()

with open(tro_file) as f:
data = json.load(f)
assert {"ex": "http://example.org/"} in data["@context"]
assert {"foaf": "http://xmlns.com/foaf/0.1/"} in data["@context"]
Loading
Loading