Skip to content

Commit ba1decf

Browse files
committed
Add missing tests
1 parent 9a48682 commit ba1decf

4 files changed

Lines changed: 518 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
"""
2+
Integration tests for WOQL DateDuration predicate.
3+
4+
Tests the tri-directional DateDuration(Start, End, Duration) predicate:
5+
- Start + End → Duration (compute duration from two dates)
6+
- Start + Duration → End (EOM-aware addition)
7+
- Duration + End → Start (EOM-aware subtraction)
8+
- All three ground (validation)
9+
- EOM reversibility
10+
"""
11+
12+
import pytest
13+
14+
from terminusdb_client import Client
15+
from terminusdb_client.woqlquery.woql_query import WOQLQuery
16+
17+
test_user_agent = "terminusdb-client-python-tests"
18+
19+
20+
def dat(v):
21+
"""Build an xsd:date typed literal."""
22+
return {"@type": "xsd:date", "@value": v}
23+
24+
25+
def dtm(v):
26+
"""Build an xsd:dateTime typed literal."""
27+
return {"@type": "xsd:dateTime", "@value": v}
28+
29+
30+
def dur(v):
31+
"""Build an xsd:duration typed literal."""
32+
return {"@type": "xsd:duration", "@value": v}
33+
34+
35+
class TestDateDurationComputeDuration:
36+
"""Start + End → Duration."""
37+
38+
@pytest.fixture(autouse=True)
39+
def setup_teardown(self, docker_url):
40+
self.client = Client(docker_url, user_agent=test_user_agent)
41+
self.client.connect()
42+
self.db_name = "test_date_dur_compute"
43+
if self.db_name in self.client.list_databases():
44+
self.client.delete_database(self.db_name)
45+
self.client.create_database(self.db_name)
46+
yield
47+
self.client.delete_database(self.db_name)
48+
49+
def test_leap_year_91_days(self):
50+
"""2024-01-01 to 2024-04-01 = P91D (leap year)."""
51+
query = WOQLQuery().date_duration(dat("2024-01-01"), dat("2024-04-01"), "v:d")
52+
result = self.client.query(query)
53+
assert len(result["bindings"]) == 1
54+
assert result["bindings"][0]["d"]["@value"] == "P91D"
55+
56+
def test_non_leap_year_90_days(self):
57+
"""2025-01-01 to 2025-04-01 = P90D (non-leap year)."""
58+
query = WOQLQuery().date_duration(dat("2025-01-01"), dat("2025-04-01"), "v:d")
59+
result = self.client.query(query)
60+
assert len(result["bindings"]) == 1
61+
assert result["bindings"][0]["d"]["@value"] == "P90D"
62+
63+
def test_zero_duration(self):
64+
"""Same date produces P0D."""
65+
query = WOQLQuery().date_duration(dat("2024-01-01"), dat("2024-01-01"), "v:d")
66+
result = self.client.query(query)
67+
assert len(result["bindings"]) == 1
68+
assert result["bindings"][0]["d"]["@value"] == "P0D"
69+
70+
def test_datetime_time_difference(self):
71+
"""dateTime with sub-day difference produces PT9H30M."""
72+
query = WOQLQuery().date_duration(
73+
dtm("2024-01-01T08:00:00Z"), dtm("2024-01-01T17:30:00Z"), "v:d"
74+
)
75+
result = self.client.query(query)
76+
assert len(result["bindings"]) == 1
77+
assert result["bindings"][0]["d"]["@value"] == "PT9H30M"
78+
79+
def test_datetime_midnight_to_midnight(self):
80+
"""Midnight-to-midnight omits time component: P3D."""
81+
query = WOQLQuery().date_duration(
82+
dtm("2024-01-01T00:00:00Z"), dtm("2024-01-04T00:00:00Z"), "v:d"
83+
)
84+
result = self.client.query(query)
85+
assert len(result["bindings"]) == 1
86+
assert result["bindings"][0]["d"]["@value"] == "P3D"
87+
88+
89+
class TestDateDurationAddition:
90+
"""Start + Duration → End (EOM-aware)."""
91+
92+
@pytest.fixture(autouse=True)
93+
def setup_teardown(self, docker_url):
94+
self.client = Client(docker_url, user_agent=test_user_agent)
95+
self.client.connect()
96+
self.db_name = "test_date_dur_add"
97+
if self.db_name in self.client.list_databases():
98+
self.client.delete_database(self.db_name)
99+
self.client.create_database(self.db_name)
100+
yield
101+
self.client.delete_database(self.db_name)
102+
103+
def test_jan31_plus_1m_leap(self):
104+
"""Jan 31 + P1M = Feb 29 (leap year EOM)."""
105+
query = WOQLQuery().date_duration(dat("2020-01-31"), "v:e", dur("P1M"))
106+
result = self.client.query(query)
107+
assert len(result["bindings"]) == 1
108+
assert result["bindings"][0]["e"]["@value"] == "2020-02-29"
109+
110+
def test_jan31_plus_1m_non_leap(self):
111+
"""Jan 31 + P1M = Feb 28 (non-leap year EOM)."""
112+
query = WOQLQuery().date_duration(dat("2021-01-31"), "v:e", dur("P1M"))
113+
result = self.client.query(query)
114+
assert len(result["bindings"]) == 1
115+
assert result["bindings"][0]["e"]["@value"] == "2021-02-28"
116+
117+
def test_feb29_plus_1m_eom_preservation(self):
118+
"""Feb 29 + P1M = Mar 31 (EOM preservation)."""
119+
query = WOQLQuery().date_duration(dat("2020-02-29"), "v:e", dur("P1M"))
120+
result = self.client.query(query)
121+
assert len(result["bindings"]) == 1
122+
assert result["bindings"][0]["e"]["@value"] == "2020-03-31"
123+
124+
def test_apr30_plus_1m_eom_preservation(self):
125+
"""Apr 30 + P1M = May 31 (EOM preservation)."""
126+
query = WOQLQuery().date_duration(dat("2020-04-30"), "v:e", dur("P1M"))
127+
result = self.client.query(query)
128+
assert len(result["bindings"]) == 1
129+
assert result["bindings"][0]["e"]["@value"] == "2020-05-31"
130+
131+
def test_dec31_plus_1m_year_boundary(self):
132+
"""Dec 31 + P1M = Jan 31 next year."""
133+
query = WOQLQuery().date_duration(dat("2020-12-31"), "v:e", dur("P1M"))
134+
result = self.client.query(query)
135+
assert len(result["bindings"]) == 1
136+
assert result["bindings"][0]["e"]["@value"] == "2021-01-31"
137+
138+
139+
class TestDateDurationSubtraction:
140+
"""Duration + End → Start (EOM-aware)."""
141+
142+
@pytest.fixture(autouse=True)
143+
def setup_teardown(self, docker_url):
144+
self.client = Client(docker_url, user_agent=test_user_agent)
145+
self.client.connect()
146+
self.db_name = "test_date_dur_sub"
147+
if self.db_name in self.client.list_databases():
148+
self.client.delete_database(self.db_name)
149+
self.client.create_database(self.db_name)
150+
yield
151+
self.client.delete_database(self.db_name)
152+
153+
def test_mar31_minus_1m_leap(self):
154+
"""Mar 31 - P1M = Feb 29 (leap year)."""
155+
query = WOQLQuery().date_duration("v:s", dat("2020-03-31"), dur("P1M"))
156+
result = self.client.query(query)
157+
assert len(result["bindings"]) == 1
158+
assert result["bindings"][0]["s"]["@value"] == "2020-02-29"
159+
160+
def test_mar31_minus_1m_non_leap(self):
161+
"""Mar 31 - P1M = Feb 28 (non-leap year)."""
162+
query = WOQLQuery().date_duration("v:s", dat("2021-03-31"), dur("P1M"))
163+
result = self.client.query(query)
164+
assert len(result["bindings"]) == 1
165+
assert result["bindings"][0]["s"]["@value"] == "2021-02-28"
166+
167+
def test_jan31_minus_1m_year_boundary(self):
168+
"""Jan 31 - P1M = Dec 31 previous year."""
169+
query = WOQLQuery().date_duration("v:s", dat("2021-01-31"), dur("P1M"))
170+
result = self.client.query(query)
171+
assert len(result["bindings"]) == 1
172+
assert result["bindings"][0]["s"]["@value"] == "2020-12-31"
173+
174+
175+
class TestDateDurationEOMReversibility:
176+
"""EOM reversibility: add then subtract returns original."""
177+
178+
@pytest.fixture(autouse=True)
179+
def setup_teardown(self, docker_url):
180+
self.client = Client(docker_url, user_agent=test_user_agent)
181+
self.client.connect()
182+
self.db_name = "test_date_dur_eom"
183+
if self.db_name in self.client.list_databases():
184+
self.client.delete_database(self.db_name)
185+
self.client.create_database(self.db_name)
186+
yield
187+
self.client.delete_database(self.db_name)
188+
189+
def test_feb29_minus_1m_equals_jan31(self):
190+
"""Feb 29 - P1M = Jan 31 (reverse of Jan 31 + P1M = Feb 29)."""
191+
query = WOQLQuery().date_duration("v:s", dat("2020-02-29"), dur("P1M"))
192+
result = self.client.query(query)
193+
assert len(result["bindings"]) == 1
194+
assert result["bindings"][0]["s"]["@value"] == "2020-01-31"
195+
196+
197+
class TestDateDurationValidation:
198+
"""All three ground — validation mode."""
199+
200+
@pytest.fixture(autouse=True)
201+
def setup_teardown(self, docker_url):
202+
self.client = Client(docker_url, user_agent=test_user_agent)
203+
self.client.connect()
204+
self.db_name = "test_date_dur_val"
205+
if self.db_name in self.client.list_databases():
206+
self.client.delete_database(self.db_name)
207+
self.client.create_database(self.db_name)
208+
yield
209+
self.client.delete_database(self.db_name)
210+
211+
def test_consistent_succeeds(self):
212+
"""Consistent start+end+duration returns one binding."""
213+
query = WOQLQuery().date_duration(
214+
dat("2024-01-01"), dat("2024-04-01"), dur("P91D")
215+
)
216+
result = self.client.query(query)
217+
assert len(result["bindings"]) == 1
218+
219+
def test_inconsistent_fails(self):
220+
"""Inconsistent start+end+duration returns zero bindings."""
221+
query = WOQLQuery().date_duration(
222+
dat("2024-01-01"), dat("2024-04-01"), dur("P90D")
223+
)
224+
result = self.client.query(query)
225+
assert len(result["bindings"]) == 0
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"""
2+
Integration tests for WOQL IntervalRelationTyped predicate.
3+
4+
Tests Allen's Interval Algebra on xdd:dateTimeInterval values:
5+
- Validation mode (relation ground)
6+
- Classification mode (relation as variable)
7+
- dateTime interval support
8+
"""
9+
10+
import pytest
11+
12+
from terminusdb_client import Client
13+
from terminusdb_client.woqlquery.woql_query import WOQLQuery
14+
15+
test_user_agent = "terminusdb-client-python-tests"
16+
17+
18+
def iv(v):
19+
"""Build an xdd:dateTimeInterval typed literal."""
20+
return {"@type": "xdd:dateTimeInterval", "@value": v}
21+
22+
23+
def rel(v):
24+
"""Build an xsd:string typed literal for the relation name."""
25+
return {"@type": "xsd:string", "@value": v}
26+
27+
28+
class TestIntervalRelationTypedValidation:
29+
"""Validation mode: relation is ground."""
30+
31+
@pytest.fixture(autouse=True)
32+
def setup_teardown(self, docker_url):
33+
self.client = Client(docker_url, user_agent=test_user_agent)
34+
self.client.connect()
35+
self.db_name = "test_irt_validate"
36+
if self.db_name in self.client.list_databases():
37+
self.client.delete_database(self.db_name)
38+
self.client.create_database(self.db_name)
39+
yield
40+
self.client.delete_database(self.db_name)
41+
42+
def test_meets(self):
43+
"""Q1 meets Q2."""
44+
query = WOQLQuery().interval_relation_typed(
45+
rel("meets"), iv("2024-01-01/2024-04-01"), iv("2024-04-01/2024-07-01")
46+
)
47+
result = self.client.query(query)
48+
assert len(result["bindings"]) == 1
49+
50+
def test_meets_rejected_with_gap(self):
51+
"""Gap between intervals fails meets."""
52+
query = WOQLQuery().interval_relation_typed(
53+
rel("meets"), iv("2024-01-01/2024-04-01"), iv("2024-05-01/2024-07-01")
54+
)
55+
result = self.client.query(query)
56+
assert len(result["bindings"]) == 0
57+
58+
def test_before(self):
59+
"""Q1 before Q3."""
60+
query = WOQLQuery().interval_relation_typed(
61+
rel("before"), iv("2024-01-01/2024-03-01"), iv("2024-06-01/2024-09-01")
62+
)
63+
result = self.client.query(query)
64+
assert len(result["bindings"]) == 1
65+
66+
def test_during(self):
67+
"""Sub-interval during year."""
68+
query = WOQLQuery().interval_relation_typed(
69+
rel("during"), iv("2024-03-01/2024-06-01"), iv("2024-01-01/2024-12-01")
70+
)
71+
result = self.client.query(query)
72+
assert len(result["bindings"]) == 1
73+
74+
def test_equals(self):
75+
"""Same interval."""
76+
query = WOQLQuery().interval_relation_typed(
77+
rel("equals"), iv("2024-01-01/2024-06-01"), iv("2024-01-01/2024-06-01")
78+
)
79+
result = self.client.query(query)
80+
assert len(result["bindings"]) == 1
81+
82+
def test_contains(self):
83+
"""FY contains Q2."""
84+
query = WOQLQuery().interval_relation_typed(
85+
rel("contains"), iv("2024-01-01/2025-01-01"), iv("2024-04-01/2024-07-01")
86+
)
87+
result = self.client.query(query)
88+
assert len(result["bindings"]) == 1
89+
90+
91+
class TestIntervalRelationTypedClassification:
92+
"""Classification mode: relation is a variable."""
93+
94+
@pytest.fixture(autouse=True)
95+
def setup_teardown(self, docker_url):
96+
self.client = Client(docker_url, user_agent=test_user_agent)
97+
self.client.connect()
98+
self.db_name = "test_irt_classify"
99+
if self.db_name in self.client.list_databases():
100+
self.client.delete_database(self.db_name)
101+
self.client.create_database(self.db_name)
102+
yield
103+
self.client.delete_database(self.db_name)
104+
105+
def test_classifies_meets(self):
106+
"""Adjacent intervals classified as meets."""
107+
query = WOQLQuery().interval_relation_typed(
108+
"v:rel", iv("2024-01-01/2024-04-01"), iv("2024-04-01/2024-07-01")
109+
)
110+
result = self.client.query(query)
111+
assert len(result["bindings"]) == 1
112+
assert result["bindings"][0]["rel"]["@value"] == "meets"
113+
114+
def test_classifies_before(self):
115+
"""Non-overlapping intervals classified as before."""
116+
query = WOQLQuery().interval_relation_typed(
117+
"v:rel", iv("2024-01-01/2024-03-01"), iv("2024-06-01/2024-09-01")
118+
)
119+
result = self.client.query(query)
120+
assert len(result["bindings"]) == 1
121+
assert result["bindings"][0]["rel"]["@value"] == "before"
122+
123+
def test_classifies_during(self):
124+
"""Nested interval classified as during."""
125+
query = WOQLQuery().interval_relation_typed(
126+
"v:rel", iv("2024-03-01/2024-06-01"), iv("2024-01-01/2024-12-01")
127+
)
128+
result = self.client.query(query)
129+
assert len(result["bindings"]) == 1
130+
assert result["bindings"][0]["rel"]["@value"] == "during"
131+
132+
def test_classifies_equals(self):
133+
"""Identical intervals classified as equals."""
134+
query = WOQLQuery().interval_relation_typed(
135+
"v:rel", iv("2024-01-01/2024-06-01"), iv("2024-01-01/2024-06-01")
136+
)
137+
result = self.client.query(query)
138+
assert len(result["bindings"]) == 1
139+
assert result["bindings"][0]["rel"]["@value"] == "equals"
140+
141+
def test_classifies_datetime_meets(self):
142+
"""dateTime intervals classified as meets."""
143+
query = WOQLQuery().interval_relation_typed(
144+
"v:rel",
145+
iv("2024-01-01T08:00:00Z/2024-01-01T12:00:00Z"),
146+
iv("2024-01-01T12:00:00Z/2024-01-01T17:00:00Z"),
147+
)
148+
result = self.client.query(query)
149+
assert len(result["bindings"]) == 1
150+
assert result["bindings"][0]["rel"]["@value"] == "meets"

0 commit comments

Comments
 (0)