''' Storage command UX regressions. ''' from collections.abc import ( AsyncIterator, Iterator, ) from contextlib import asynccontextmanager import json from pathlib import Path from types import SimpleNamespace from typing import Any import numpy as np import pytest from typer.testing import CliRunner from piker import tsp from piker import config from piker.data import def_iohlcv_fields from piker.storage import cli as storage_cli from piker.storage.cli import store def test_store_group_shows_help_without_arguments() -> None: ''' Bare ``piker store`` must present its command map immediately. The Typer group emitted only a missing-command error and required another ``--help`` invocation. Invoke the group without arguments and prove complete help replaces that error. ''' result = CliRunner().invoke(store, []) assert result.exit_code == 2 assert 'Usage:' in result.output assert 'Missing command' not in result.output for command in ( 'anal', 'audit', 'delete', 'ldshm', 'ls', 'series', 'shm', ): assert command in result.output def test_store_commands_show_help_without_arguments() -> None: ''' Bare endpoints must not open runtimes or report missing args. Every endpoint is discoverable by typing its name once. Exercise both required-input and explicitly-triggered listing commands and prove help rendering exits before any callback can run. ''' runner = CliRunner() for command in ( 'anal', 'audit', 'delete', 'ldshm', 'ls', 'series', 'shm', ): result = runner.invoke(store, [command]) assert result.exit_code == 2 assert 'Usage:' in result.output assert 'Missing argument' not in result.output def test_series_lists_exact_native_periods( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: ''' Durable listing must expose separate 1s and 60s identities. The old FQME-only index hid one timeframe. Create canonical files without storage or actor services, invoke the JSON endpoint, and prove both exact paths and periods are returned. ''' nativedb: Path = tmp_path / 'nativedb' nativedb.mkdir() one = nativedb / 'qqq.nasdaq.ib.ohlcv1s.parquet' sixty = nativedb / 'qqq.nasdaq.ib.ohlcv60s.parquet' one.write_bytes(b'1') sixty.write_bytes(b'60') monkeypatch.setattr(config, 'get_conf_dir', lambda: tmp_path) result = CliRunner().invoke( store, ['series', '--all', '--json'], ) assert result.exit_code == 0 payload = json.loads(result.output) assert [item['period_s'] for item in payload] == [1, 60] assert [item['path'] for item in payload] == [ str(one), str(sixty), ] def test_shm_endpoint_reports_immutable_snapshot( monkeypatch: pytest.MonkeyPatch, ) -> None: ''' SHM diagnostics report exact buffers without storage mutation. Build one RT identity and snapshot, then replace runtime attachment with fakes. Invoke JSON output and prove it carries generation, cadence, and invalid-slot evidence without opening a storage client or writer. ''' path = Path( 'piker.datad[aaaaaaaa-aaaa-aa].qqq.nasdaq.ib.rt' ) ref = tsp.ShmBufferRef( service='datad', generation='aaaaaaaa-aaaa-aa', fqme='qqq.nasdaq.ib', kind='rt', path=path, ) frame = np.zeros( 4, dtype=np.dtype(def_iohlcv_fields), ) frame['index'] = np.arange(4) frame['time'] = [1, 0, 3, 4] @asynccontextmanager async def open_runtime( *args: Any, **kwargs: Any, ) -> AsyncIterator[None]: ''' Yield a runtime-free diagnostic context. ''' yield def iter_refs( fqme: str|None = None, ) -> Iterator[tsp.ShmBufferRef]: ''' Yield the selected exact SHM identity. ''' yield ref def iter_frames( fqme: str, shm_name: str|None = None, refs: list[tsp.ShmBufferRef]|None = None, ) -> Iterator[tuple[Path, SimpleNamespace, None]]: ''' Yield an immutable snapshot through the attachment interface. ''' shm = SimpleNamespace(array=frame) yield path, shm, None def fail_storage(*args: Any, **kwargs: Any) -> None: ''' Fail if read-only SHM inspection reaches storage. ''' raise AssertionError('SHM diagnostics opened storage') monkeypatch.setattr( storage_cli, 'open_piker_runtime', open_runtime, ) monkeypatch.setattr( storage_cli, 'open_storage_client', fail_storage, ) monkeypatch.setattr(tsp, 'iter_shm_buffer_refs', iter_refs) monkeypatch.setattr(tsp, 'iter_dfs_from_shms', iter_frames) result = CliRunner().invoke( store, ['shm', 'qqq.nasdaq.ib', '--json'], ) assert result.exit_code == 0 payload = json.loads(result.output) assert len(payload) == 1 assert payload[0]['generation'] == 'aaaaaaaa-aaaa-aa' assert payload[0]['period_s'] == 1 assert payload[0]['invalid_count'] == 1 def test_ldshm_rejects_unknown_exact_name_before_runtime( monkeypatch: pytest.MonkeyPatch, ) -> None: ''' A stale exact SHM selection must fail before opening services. The old no-match path paused after opening runtime and storage setup. Return no identity, install a runtime fail-spy, and prove exact selection exits immediately with a deterministic error. ''' def iter_refs( fqme: str|None = None, ) -> Iterator[tsp.ShmBufferRef]: ''' Yield no matching SHM identities. ''' return iter(()) def fail_runtime(*args: Any, **kwargs: Any) -> None: ''' Fail if no-match handling opens a runtime. ''' raise AssertionError('unknown SHM opened runtime') monkeypatch.setattr(tsp, 'iter_shm_buffer_refs', iter_refs) monkeypatch.setattr( storage_cli, 'open_piker_runtime', fail_runtime, ) result = CliRunner().invoke( store, [ 'ldshm', 'qqq.nasdaq.ib', '--shm-name', 'missing', ], ) assert result.exit_code == 2 assert 'No exact OHLCV SHM buffer' in result.output