aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_startup_scan_enrichment.py
blob: f43c6e9544064e491c1a3cd6cfd4b881f7c4b843 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
"""
Regression: indexer.initial_scan() (run once at startup, daemon.py's
_bg_scan) never itself calls on_change — that predates the Videos app, and
every existing caller only cared about the scan finishing, not about
notifying anyone. Enrichment (duration/thumb_hash/display_title/...) hangs
entirely off on_change (daemon._broadcast_index_change).

Without an explicit call to _on_index_change right after the startup scan,
a file already on disk at boot — the common case, an existing library —
would never get enriched at all: only a file added later, while the node
is already running (seen by the watchdog), would trigger it. Found live
against a real library after the first restart with this feature enabled.

Enrichment only runs once a group has a video_root configured (a group
with none set gets no TMDB/thumbnail work at all, docs/MESHBAY_DESIGN.md
§6.5) — this test's fake roster reports the shared root itself as the
configured video_root, so the enrichment-scheduling behaviour under test
is exercised the same way a real operator's group would be.
"""

import asyncio

import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from unittest.mock import MagicMock

from meshbay_common.crypto import generate_gek
from meshbay_node.config import Config, HubConfig, NodeConfig, GroupConfig, KeystoreConfig
from meshbay_node.daemon import NodeDaemon
from meshbay_node.indexer import DirectoryIndexer
from meshbay_node.indexer.enrich import Enricher
from meshbay_node.media_cache import MediaCache

from conftest import one_root

pytestmark = pytest.mark.asyncio


def _free_port() -> int:
    import socket
    with socket.socket() as s:
        s.bind(("127.0.0.1", 0))
        return s.getsockname()[1]


async def test_a_file_already_on_disk_at_startup_gets_enrichment_scheduled(tmp_path):
    shared = tmp_path / "shared"
    shared.mkdir()
    (shared / "movie.mkv").write_bytes(b"not a real video, just needs to be indexed as one")

    config = Config(
        hub=HubConfig(url="http://localhost:9999", username="testuser"),
        node=NodeConfig(quic_port=_free_port(), ui_port=_free_port()),
        groups=[GroupConfig(
            id="a" * 32, name="test-group", shared_dir=str(shared),
            visibility="private", quic_port=29012,
        )],
        keystore=KeystoreConfig(path=tmp_path / "keystore.enc"),
        data_dir=tmp_path / "data",
    )
    class _StubRoster:
        async def app_directories(self, group_id, app_key):
            # The root itself, i.e. "enrich the whole thing".
            return ["shared"] if app_key == "video" else []

    daemon = NodeDaemon(config)
    daemon._broadcast_coalesce_secs = 0.01  # real value would make this test wait 0.5s
    daemon._media_cache = MediaCache(db_path=tmp_path / "media_cache.db")
    await daemon._media_cache.open()
    daemon._enricher = Enricher(daemon._media_cache)
    daemon._roster = _StubRoster()

    sk_node = Ed25519PrivateKey.generate()
    indexer = DirectoryIndexer(
        roots=one_root(shared), group_id="a" * 32,
        sk_node=sk_node, gek=generate_gek())

    # Mirrors _bg_scan's actual sequence in daemon.py.
    await indexer.initial_scan()
    assert not daemon._enriched_attempted, (
        "nothing should be scheduled before _on_index_change is ever called")

    await daemon._on_index_change(indexer)
    await asyncio.sleep(0.05)  # let the coalescing timer fire _broadcast_index_change

    entry = next(iter(indexer.index.entries))
    assert ("a" * 32, entry.id) in daemon._enriched_attempted, (
        "a file already on disk at startup must get enrichment scheduled the "
        "first time its group's index is broadcast, not only on a later "
        "watchdog-detected change to it")

    await daemon._media_cache.close()