Skip to content

Commit b617efa

Browse files
committed
Initial iso8601 interval processing
1 parent 512f684 commit b617efa

1 file changed

Lines changed: 193 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"""
2+
Integration tests for WOQL interval duration predicates.
3+
4+
These tests verify the new interval features:
5+
- Interval with xsd:dateTime endpoints
6+
- IntervalStartDuration (start + duration decomposition)
7+
- IntervalDurationEnd (duration + end decomposition)
8+
- Roundtrip consistency across all three interval views
9+
"""
10+
11+
import pytest
12+
13+
from terminusdb_client import Client
14+
from terminusdb_client.woqlquery.woql_query import WOQLQuery
15+
16+
test_user_agent = "terminusdb-client-python-tests"
17+
18+
19+
def dat(v):
20+
"""Build an xsd:date typed literal."""
21+
return {"@type": "xsd:date", "@value": v}
22+
23+
24+
def dtm(v):
25+
"""Build an xsd:dateTime typed literal."""
26+
return {"@type": "xsd:dateTime", "@value": v}
27+
28+
29+
def dur(v):
30+
"""Build an xsd:duration typed literal."""
31+
return {"@type": "xsd:duration", "@value": v}
32+
33+
34+
def dti(v):
35+
"""Build an xdd:dateTimeInterval typed literal."""
36+
return {"@type": "xdd:dateTimeInterval", "@value": v}
37+
38+
39+
class TestIntervalDateTimeEndpoints:
40+
"""Tests for Interval predicate with xsd:dateTime endpoints."""
41+
42+
@pytest.fixture(autouse=True)
43+
def setup_teardown(self, docker_url):
44+
self.client = Client(docker_url, user_agent=test_user_agent)
45+
self.client.connect()
46+
self.db_name = "test_interval_duration"
47+
if self.db_name in self.client.list_databases():
48+
self.client.delete_database(self.db_name)
49+
self.client.create_database(self.db_name)
50+
yield
51+
self.client.delete_database(self.db_name)
52+
53+
def test_construct_from_datetime_endpoints(self):
54+
"""Construct interval from two xsd:dateTime values."""
55+
query = WOQLQuery().interval(dtm("2025-01-01T09:00:00Z"),
56+
dtm("2025-01-01T17:30:00Z"),
57+
"v:iv")
58+
result = self.client.query(query)
59+
assert len(result["bindings"]) == 1
60+
assert result["bindings"][0]["iv"]["@value"] == \
61+
"2025-01-01T09:00:00Z/2025-01-01T17:30:00Z"
62+
63+
def test_construct_mixed_date_datetime(self):
64+
"""Construct interval with date start and dateTime end."""
65+
query = WOQLQuery().interval(dat("2025-01-01"),
66+
dtm("2025-04-01T12:00:00Z"),
67+
"v:iv")
68+
result = self.client.query(query)
69+
assert len(result["bindings"]) == 1
70+
assert result["bindings"][0]["iv"]["@value"] == \
71+
"2025-01-01/2025-04-01T12:00:00Z"
72+
73+
def test_deconstruct_datetime_interval(self):
74+
"""Deconstruct dateTime interval preserves types."""
75+
query = WOQLQuery().interval("v:s", "v:e",
76+
dti("2025-01-01T09:00:00Z/2025-04-01T17:30:00Z"))
77+
result = self.client.query(query)
78+
assert len(result["bindings"]) == 1
79+
assert result["bindings"][0]["s"]["@type"] == "xsd:dateTime"
80+
assert result["bindings"][0]["e"]["@type"] == "xsd:dateTime"
81+
82+
83+
class TestIntervalStartDuration:
84+
"""Tests for IntervalStartDuration predicate."""
85+
86+
@pytest.fixture(autouse=True)
87+
def setup_teardown(self, docker_url):
88+
self.client = Client(docker_url, user_agent=test_user_agent)
89+
self.client.connect()
90+
self.db_name = "test_interval_start_dur"
91+
if self.db_name in self.client.list_databases():
92+
self.client.delete_database(self.db_name)
93+
self.client.create_database(self.db_name)
94+
yield
95+
self.client.delete_database(self.db_name)
96+
97+
def test_extract_start_and_duration_from_date_interval(self):
98+
"""Extract start + P90D duration from a 90-day interval."""
99+
query = WOQLQuery().interval_start_duration(
100+
"v:s", "v:d", dti("2025-01-01/2025-04-01"))
101+
result = self.client.query(query)
102+
assert len(result["bindings"]) == 1
103+
assert result["bindings"][0]["s"]["@value"] == "2025-01-01"
104+
assert result["bindings"][0]["d"]["@value"] == "P90D"
105+
106+
def test_extract_sub_day_duration_from_datetime_interval(self):
107+
"""Extract PT8H30M duration from a sub-day dateTime interval."""
108+
query = WOQLQuery().interval_start_duration(
109+
"v:s", "v:d",
110+
dti("2025-01-01T09:00:00Z/2025-01-01T17:30:00Z"))
111+
result = self.client.query(query)
112+
assert len(result["bindings"]) == 1
113+
assert result["bindings"][0]["s"]["@type"] == "xsd:dateTime"
114+
assert result["bindings"][0]["d"]["@value"] == "PT8H30M"
115+
116+
def test_construct_interval_from_start_and_duration(self):
117+
"""Construct [Jan 1, Apr 1) from start date + P90D."""
118+
query = WOQLQuery().interval_start_duration(
119+
dat("2025-01-01"), dur("P90D"), "v:iv")
120+
result = self.client.query(query)
121+
assert len(result["bindings"]) == 1
122+
assert result["bindings"][0]["iv"]["@value"] == "2025-01-01/2025-04-01"
123+
124+
def test_construct_full_year_interval(self):
125+
"""Construct a 365-day interval from start + P365D."""
126+
query = WOQLQuery().interval_start_duration(
127+
dat("2025-01-01"), dur("P365D"), "v:iv")
128+
result = self.client.query(query)
129+
assert len(result["bindings"]) == 1
130+
assert result["bindings"][0]["iv"]["@value"] == "2025-01-01/2026-01-01"
131+
132+
133+
class TestIntervalDurationEnd:
134+
"""Tests for IntervalDurationEnd predicate."""
135+
136+
@pytest.fixture(autouse=True)
137+
def setup_teardown(self, docker_url):
138+
self.client = Client(docker_url, user_agent=test_user_agent)
139+
self.client.connect()
140+
self.db_name = "test_interval_dur_end"
141+
if self.db_name in self.client.list_databases():
142+
self.client.delete_database(self.db_name)
143+
self.client.create_database(self.db_name)
144+
yield
145+
self.client.delete_database(self.db_name)
146+
147+
def test_extract_duration_and_end_from_interval(self):
148+
"""Extract P90D + end date from a 90-day interval."""
149+
query = WOQLQuery().interval_duration_end(
150+
"v:d", "v:e", dti("2025-01-01/2025-04-01"))
151+
result = self.client.query(query)
152+
assert len(result["bindings"]) == 1
153+
assert result["bindings"][0]["e"]["@value"] == "2025-04-01"
154+
assert result["bindings"][0]["d"]["@value"] == "P90D"
155+
156+
def test_construct_interval_from_duration_and_end(self):
157+
"""Construct [Jan 1, Apr 1) from P90D + end date."""
158+
query = WOQLQuery().interval_duration_end(
159+
dur("P90D"), dat("2025-04-01"), "v:iv")
160+
result = self.client.query(query)
161+
assert len(result["bindings"]) == 1
162+
assert result["bindings"][0]["iv"]["@value"] == "2025-01-01/2025-04-01"
163+
164+
165+
class TestIntervalThreeViewsRoundtrip:
166+
"""Roundtrip test: all three decomposition views of the same interval agree."""
167+
168+
@pytest.fixture(autouse=True)
169+
def setup_teardown(self, docker_url):
170+
self.client = Client(docker_url, user_agent=test_user_agent)
171+
self.client.connect()
172+
self.db_name = "test_interval_roundtrip"
173+
if self.db_name in self.client.list_databases():
174+
self.client.delete_database(self.db_name)
175+
self.client.create_database(self.db_name)
176+
yield
177+
self.client.delete_database(self.db_name)
178+
179+
def test_three_views_agree(self):
180+
"""start/end, start/duration, and duration/end all agree."""
181+
iv = dti("2025-01-01/2025-04-01")
182+
query = WOQLQuery().woql_and(
183+
WOQLQuery().interval("v:s1", "v:e1", iv),
184+
WOQLQuery().interval_start_duration("v:s2", "v:d2", iv),
185+
WOQLQuery().interval_duration_end("v:d3", "v:e3", iv),
186+
)
187+
result = self.client.query(query)
188+
assert len(result["bindings"]) == 1
189+
b = result["bindings"][0]
190+
assert b["s1"]["@value"] == b["s2"]["@value"]
191+
assert b["e1"]["@value"] == b["e3"]["@value"]
192+
assert b["d2"]["@value"] == b["d3"]["@value"]
193+
assert b["d2"]["@value"] == "P90D"

0 commit comments

Comments
 (0)