@@ -356,3 +356,95 @@ async def test_find_by_category_case_sensitivity(
356356
357357 upper_case = await repo .find_by_category ("TECH" )
358358 assert len (upper_case ) == 0 # Currently case-sensitive
359+
360+
361+ @pytest .mark .asyncio
362+ async def test_observation_permalink_truncates_long_content (
363+ session_maker : async_sessionmaker , repo , test_project : Project
364+ ):
365+ """Test that observation permalinks truncate long content.
366+
367+ This test validates the fix for issue #446 where:
368+ - Long observation content (like transcript dialogue) created permalinks
369+ exceeding PostgreSQL's btree index limit of 2704 bytes.
370+ - Content is now truncated to 200 chars in the permalink property.
371+ """
372+ async with db .scoped_session (session_maker ) as session :
373+ entity = Entity (
374+ project_id = test_project .id ,
375+ title = "test_entity" ,
376+ entity_type = "test" ,
377+ permalink = "test/test-entity" ,
378+ file_path = "test/test_entity.md" ,
379+ content_type = "text/markdown" ,
380+ created_at = datetime .now (timezone .utc ),
381+ updated_at = datetime .now (timezone .utc ),
382+ )
383+ session .add (entity )
384+ await session .flush ()
385+
386+ # Create observation with very long content (5000+ chars to simulate transcript)
387+ long_content = "A" * 5000 # Well over the 200 char limit
388+ obs = Observation (
389+ entity_id = entity .id ,
390+ content = long_content ,
391+ category = "transcript" ,
392+ )
393+ session .add (obs )
394+ await session .flush ()
395+
396+ # Access the permalink property
397+ permalink = obs .permalink
398+
399+ # The full content would create a permalink like:
400+ # test/test-entity/observations/transcript/AAAA...5000 chars
401+ # With truncation, it should be much shorter
402+
403+ # Content portion should be truncated to 200 chars
404+ # Permalink format: entity_permalink/observations/category/content
405+ assert len (permalink ) < 300 # Should be well under 300 chars total
406+ assert len (long_content [:200 ]) == 200 # Verify truncation length
407+
408+ # Verify the permalink contains expected parts
409+ assert "test/test-entity" in permalink or "test-entity" in permalink
410+ assert "observations" in permalink
411+ assert "transcript" in permalink
412+
413+ # Full 5000-char content should NOT be in permalink
414+ assert long_content not in permalink
415+
416+
417+ @pytest .mark .asyncio
418+ async def test_observation_permalink_short_content_unchanged (
419+ session_maker : async_sessionmaker , repo , test_project : Project
420+ ):
421+ """Test that short observation content is not unnecessarily truncated."""
422+ async with db .scoped_session (session_maker ) as session :
423+ entity = Entity (
424+ project_id = test_project .id ,
425+ title = "test_entity" ,
426+ entity_type = "test" ,
427+ permalink = "test/test-entity" ,
428+ file_path = "test/test_entity.md" ,
429+ content_type = "text/markdown" ,
430+ created_at = datetime .now (timezone .utc ),
431+ updated_at = datetime .now (timezone .utc ),
432+ )
433+ session .add (entity )
434+ await session .flush ()
435+
436+ # Create observation with short content
437+ short_content = "Short observation content"
438+ obs = Observation (
439+ entity_id = entity .id ,
440+ content = short_content ,
441+ category = "note" ,
442+ )
443+ session .add (obs )
444+ await session .flush ()
445+
446+ permalink = obs .permalink
447+
448+ # Short content should be fully included (after permalink normalization)
449+ # The generate_permalink function normalizes the content
450+ assert "short-observation-content" in permalink .lower ()
0 commit comments