Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ WORKDIR /workspace
CMD ["sleep", "infinity"]

# Build frontend on the native builder because the emitted assets are architecture-independent.
FROM --platform=$BUILDPLATFORM node:24.22.1-alpine@sha256:06d7adf3e0ef61a13f1d28682726366f97a15aa1888c934d3ecec86c33918b53 AS frontend-builder
FROM --platform=$BUILDPLATFORM node:24.22.2-alpine@sha256:8a32c8cf649a49915a66e81e7a609223c708d0adabf288ee3f4077d765542f85 AS frontend-builder
WORKDIR /app
COPY frontend/package*.json ./
COPY frontend/scripts ./scripts
Expand Down
18 changes: 16 additions & 2 deletions backend/tests/test_directory_index_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -4971,12 +4971,15 @@ def failed_hint(parent):


@pytest.mark.asyncio
@pytest.mark.parametrize("yield_after_parent", [False, True])
@pytest.mark.parametrize("failed", [False, True])
@pytest.mark.parametrize(
"operation,prefix",
[("create", ""), ("create", "scoped"), ("archive", "scoped"), ("mixed_archive", "scoped"), ("upload", "scoped")],
)
async def test_smb_mutation_queues_parent_before_dispatch_and_after_exit(sqlite_cache, monkeypatch, failed, prefix, operation):
async def test_smb_mutation_queues_parent_before_dispatch_and_after_exit(
sqlite_cache, monkeypatch, failed, prefix, operation, yield_after_parent
):
from contextlib import asynccontextmanager, contextmanager
from io import BytesIO
from pathlib import PureWindowsPath
Expand All @@ -4986,10 +4989,12 @@ async def test_smb_mutation_queues_parent_before_dispatch_and_after_exit(sqlite_

from app.api.archive_operations import _ensure_mixed_archive_parent_directories
from app.models.file import FileType
from app.services import directory_cache
from app.services.archive.extraction import _ensure_live_directory
from app.storage.smb import SMBBackend

index = sqlite_cache.index
clock = [sqlite_cache._last_access]
index.write_paths(["parent"])
assert index.finish_generation(1)
before = index.search("parent")
Expand Down Expand Up @@ -5031,6 +5036,8 @@ def scan(path, **kwargs):
parent, _, name = directory.rpartition("/")
if parent == relative:
yield SimpleNamespace(name=name, smb_info=SimpleNamespace(file_attributes=FileAttributes.FILE_ATTRIBUTE_DIRECTORY))
if yield_after_parent:
clock[0] += directory_cache.WORKER_POLL_SECONDS

@contextmanager
def open_writer(path, **kwargs):
Expand Down Expand Up @@ -5068,7 +5075,14 @@ def write(chunk):
sqlite_cache.coordinator.apply_pending()
assert set(index.pending_scan_paths(1)) == {relative_path(path).rpartition("/")[0] for path in dispatched}
assert index.search("parent") == before
sqlite_cache._reconcile_disk_queue()
monkeypatch.setattr(directory_cache, "time", SimpleNamespace(monotonic=lambda: clock[0], time=directory_cache.time.time))
# Reconciliation can yield after each parent; include the source root in the turn bound.
for reconciliation_turn in range(len(source_directories) + 1):
sqlite_cache._reconcile_disk_queue()
if index.reconciliation_generation() is None:
break
if yield_after_parent:
assert reconciliation_turn > 0
after = sqlite_cache.search_index("parent new")
assert {row.path for row in after.results} == source_directories
assert after.total_matches == after.directory_count == len(source_directories)
Expand Down
Loading