- Fix list_by_type to return node_id (IPFS CID) instead of local hash - Fix effects count on home page (count from _effects/ directory) - Add nav_counts to all page templates (recipes, effects, runs, media, storage) - Add editable metadata section to cache/media detail page - Show more metadata on recipe detail page (ID, IPFS CID, step count) - Update tests for new list_by_type behavior Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
5.7 KiB
Python
151 lines
5.7 KiB
Python
"""
|
|
Tests for recipe visibility in web UI.
|
|
|
|
Bug found 2026-01-12: Recipes not showing in list even after upload.
|
|
"""
|
|
|
|
import pytest
|
|
from pathlib import Path
|
|
|
|
|
|
class TestRecipeListingFlow:
|
|
"""Tests for recipe listing data flow."""
|
|
|
|
def test_cache_manager_has_list_by_type(self) -> None:
|
|
"""L1CacheManager should have list_by_type method."""
|
|
# Read cache_manager.py and verify list_by_type exists
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
assert 'def list_by_type' in content, \
|
|
"L1CacheManager should have list_by_type method"
|
|
|
|
def test_list_by_type_returns_node_id(self) -> None:
|
|
"""list_by_type should return entry.node_id values (IPFS CID)."""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
# Find list_by_type function and verify it appends entry.node_id
|
|
assert 'cids.append(entry.node_id)' in content, \
|
|
"list_by_type should append entry.node_id (IPFS CID) to results"
|
|
|
|
def test_recipe_service_uses_cache_list_by_type(self) -> None:
|
|
"""Recipe service should use cache.list_by_type('recipe')."""
|
|
path = Path('/home/giles/art/art-celery/app/services/recipe_service.py')
|
|
content = path.read_text()
|
|
|
|
assert "list_by_type('recipe')" in content, \
|
|
"Recipe service should call list_by_type with 'recipe'"
|
|
|
|
def test_recipe_upload_uses_recipe_node_type(self) -> None:
|
|
"""Recipe upload should store with node_type='recipe'."""
|
|
path = Path('/home/giles/art/art-celery/app/services/recipe_service.py')
|
|
content = path.read_text()
|
|
|
|
assert 'node_type="recipe"' in content, \
|
|
"Recipe upload should use node_type='recipe'"
|
|
|
|
def test_get_by_cid_uses_find_by_cid(self) -> None:
|
|
"""get_by_cid should use cache.find_by_cid to locate entries."""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
# Verify get_by_cid uses find_by_cid
|
|
assert 'find_by_cid(cid)' in content, \
|
|
"get_by_cid should use find_by_cid to locate entries"
|
|
|
|
def test_no_duplicate_get_by_cid_methods(self) -> None:
|
|
"""
|
|
Regression test: There should only be ONE get_by_cid method.
|
|
|
|
Bug: Two get_by_cid methods existed, the second shadowed the first,
|
|
breaking recipe lookup because the comprehensive method was hidden.
|
|
"""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
# Count occurrences of 'def get_by_cid'
|
|
count = content.count('def get_by_cid')
|
|
assert count == 1, \
|
|
f"Should have exactly 1 get_by_cid method, found {count}"
|
|
|
|
|
|
class TestRecipeFilterLogic:
|
|
"""Tests for recipe filtering logic."""
|
|
|
|
def test_filter_allows_none_owner(self) -> None:
|
|
"""Recipes with owner=None should be visible."""
|
|
path = Path('/home/giles/art/art-celery/app/services/recipe_service.py')
|
|
content = path.read_text()
|
|
|
|
assert 'owner is None' in content, \
|
|
"Recipe filter should allow owner=None recipes"
|
|
|
|
def test_filter_allows_matching_owner(self) -> None:
|
|
"""Recipes with matching owner should be visible."""
|
|
path = Path('/home/giles/art/art-celery/app/services/recipe_service.py')
|
|
content = path.read_text()
|
|
|
|
assert 'owner == actor_id' in content, \
|
|
"Recipe filter should allow recipes where owner matches actor_id"
|
|
|
|
|
|
class TestCacheEntryHasCid:
|
|
"""Tests for cache entry cid field."""
|
|
|
|
def test_artdag_cache_entry_has_cid(self) -> None:
|
|
"""artdag CacheEntry should have cid field."""
|
|
from artdag import CacheEntry
|
|
import dataclasses
|
|
|
|
fields = {f.name for f in dataclasses.fields(CacheEntry)}
|
|
assert 'cid' in fields, \
|
|
"CacheEntry should have cid field"
|
|
|
|
def test_artdag_cache_put_sets_cid(self) -> None:
|
|
"""artdag Cache.put should set cid on entry."""
|
|
from artdag import Cache
|
|
import inspect
|
|
|
|
source = inspect.getsource(Cache.put)
|
|
assert 'cid=' in source, \
|
|
"Cache.put should set cid on entry"
|
|
|
|
|
|
class TestListByTypeReturnsEntries:
|
|
"""Tests for list_by_type returning cached entries."""
|
|
|
|
def test_list_by_type_iterates_cache_entries(self) -> None:
|
|
"""list_by_type should iterate self.cache.list_entries()."""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
assert 'self.cache.list_entries()' in content, \
|
|
"list_by_type should iterate cache entries"
|
|
|
|
def test_list_by_type_filters_by_node_type(self) -> None:
|
|
"""list_by_type should filter entries by node_type."""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
assert 'entry.node_type == node_type' in content, \
|
|
"list_by_type should filter by node_type"
|
|
|
|
def test_list_by_type_returns_node_id(self) -> None:
|
|
"""list_by_type should return entry.node_id (IPFS CID)."""
|
|
path = Path('/home/giles/art/art-celery/cache_manager.py')
|
|
content = path.read_text()
|
|
|
|
assert 'cids.append(entry.node_id)' in content, \
|
|
"list_by_type should append entry.node_id (IPFS CID)"
|
|
|
|
def test_artdag_cache_list_entries_returns_all(self) -> None:
|
|
"""artdag Cache.list_entries should return all entries."""
|
|
from artdag import Cache
|
|
import inspect
|
|
|
|
source = inspect.getsource(Cache.list_entries)
|
|
# Should return self._entries.values()
|
|
assert '_entries' in source, \
|
|
"list_entries should access _entries dict"
|