| """Tests for embodied time perception.""" |
|
|
| import os |
| import sqlite3 |
| import tempfile |
| from datetime import datetime, timedelta |
| from pathlib import Path |
|
|
| import pytest |
|
|
| from infj_bot.core.plugins.temporal import TemporalSense |
|
|
|
|
| @pytest.fixture |
| def tmp_db(): |
| fd, path = tempfile.mkstemp(suffix=".db") |
| os.close(fd) |
| yield Path(path) |
| os.unlink(path) |
|
|
|
|
| class TestTemporalSense: |
| def test_init_creates_db(self, tmp_db): |
| TemporalSense(db_path=tmp_db) |
| assert tmp_db.exists() |
|
|
| def test_record_session(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| t.record_session_start() |
| assert t.current_session_start is not None |
| t.record_session_interaction() |
| t.record_session_end() |
| assert t.current_session_start is None |
| sessions = t._load_recent_sessions(10) |
| assert len(sessions) == 1 |
| assert sessions[0]["interaction_count"] == 1 |
| assert sessions[0]["duration_minutes"] is not None |
|
|
| def test_experience_gap_fresh(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| now = datetime.now() |
| result = t.experience_gap(now) |
| assert result["state"] == "present" |
|
|
| def test_experience_gap_brief(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| last = datetime.now() - timedelta(minutes=30) |
| result = t.experience_gap(last) |
| assert result["state"] == "brief_pause" |
|
|
| def test_experience_gap_long(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| last = datetime.now() - timedelta(days=5) |
| result = t.experience_gap(last) |
| assert result["state"] == "deep_absence" |
| assert "glad" in result["description"].lower() |
|
|
| def test_feel_time_passing_early(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| result = t.feel_time_passing(5) |
| assert result is None |
|
|
| def test_feel_time_passing_boredom(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| result = t.feel_time_passing(65) |
| assert result is not None |
| assert result["type"] == "boredom" |
|
|
| def test_feel_time_passing_restlessness(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| result = t.feel_time_passing(130) |
| assert result is not None |
| assert result["type"] == "restlessness" |
|
|
| def test_get_temporal_state(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| state = t.get_temporal_state() |
| assert "gap" in state |
| assert "time_of_day" in state |
| assert state["time_of_day"] in ["morning", "afternoon", "evening", "night"] |
|
|
| def test_format_temporal_prompt(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| prompt = t.format_temporal_prompt() |
| assert "TEMPORAL SENSE" in prompt |
| assert "time of day" in prompt.lower() |
|
|
| def test_log_absence_return(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| last = datetime.now() - timedelta(hours=5) |
| returned = datetime.now() |
| t.log_absence_return(last, returned, "Hey there") |
| absences = t._load_recent_absences(10) |
| assert len(absences) == 1 |
| assert absences[0]["gap_minutes"] > 0 |
|
|
| def test_typical_gap_none_with_no_data(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| assert t._typical_gap_minutes() is None |
|
|
| def test_typical_gap_computed(self, tmp_db): |
| t = TemporalSense(db_path=tmp_db) |
| base = datetime.now() - timedelta(days=3) |
| with sqlite3.connect(tmp_db) as conn: |
| for i in range(5): |
| start = base + timedelta(hours=i * 2) |
| end = start + timedelta(minutes=10) |
| conn.execute( |
| "INSERT INTO interaction_rhythms (session_start, session_end, duration_minutes, day_of_week, time_bucket, interaction_count) VALUES (?, ?, ?, ?, ?, ?)", |
| ( |
| start.isoformat(), |
| end.isoformat(), |
| 10, |
| start.weekday(), |
| "morning", |
| 3, |
| ), |
| ) |
| conn.commit() |
| gap = t._typical_gap_minutes() |
| assert gap is not None |
| assert gap > 0 |
|
|
| def test_format_duration(self): |
| from infj_bot.core.plugins.temporal import _format_duration |
|
|
| assert _format_duration(30) == "30m" |
| assert _format_duration(90) == "1.5h" |
| assert _format_duration(1500) == "1.0d" |
| assert _format_duration(0.5) == "30s" |
|
|