Files
Aura/crates/aura-ingest/tests/open_ohlc_absent_archive.rs
T
Brummel 8c9a1b4630 feat(aura-ingest): consolidate the real-data source-open seam
A real OHLC source now builds from aura-ingest alone, in the engine-native
epoch-ns Timestamp currency, with the one ms<->ns crossing owned by the seam.

Library surface (src/lib.rs), all additive — the ms-based `open` and
`unix_ms_to_epoch_ns` are untouched, so every existing call site is preserved
byte-for-byte:
- epoch_ns_to_unix_ms (private): the seam-owned inverse; consumers never
  convert (#80).
- M1FieldSource::open_window: the Timestamp-window mirror of `open`, mapping
  each bound through the private inverse and delegating to `open`.
- open_ohlc: the canonical OHLC bundle opener — four M1FieldSources in the fixed
  open/high/low/close C4 merge order, sharing one Arc<DataServer> (one cache,
  C12); the order lives in one vetted place (#92).
- default_data_server + `pub use data_server::{DataServer, DEFAULT_DATA_PATH}`:
  a real-data source builds without naming the external data_server crate (#81).

Consumer migration: the shared breakout_real.rs helpers and every GER40
example/test move to the Timestamp-native surface — open_ohlc_sources removed,
utc_month_window_ms -> utc_month_window (Timestamp), report_from_trace retyped,
the per-consumer ns_to_ms hand-divides deleted, the direct data_server imports
replaced by the re-exports. ger40_breakout_compare.rs (which opened OHLC by its
own hand-loop, reached by neither retyped helper) is migrated by inspection.

Return type is Vec, not [_; 4]: Harness::run consumes Vec<Box<dyn Source>>, so
it feeds straight in; order-safety comes from the single helper either way.

Tests: a hermetic round-trip pinning the inverse; a gated behaviour-preservation
test (open_ohlc Timestamp path == ms-path open, bit-identical recorded series);
a hermetic absent-archive fixture pinning the file-level None contract and the
#81 re-export firewall.

Verified: cargo build --workspace --all-targets clean; cargo test --workspace
green (the gated GER40 tests ran against the local archive — open_ohlc_seam
proves byte-identity on real data, not a skip); clippy --all-targets -D warnings
clean. Acceptance greps pass: open_ohlc_sources gone, no consumer-side ns_to_ms,
no data_server import in any migrated GER40 consumer, epoch_ns_to_unix_ms private.

closes #80, #81, #92
2026-06-18 12:59:02 +02:00

103 lines
5.3 KiB
Rust

//! Hermetic coverage for the Task-1 OHLC source-open surface (#80/#81/#92): the
//! documented *file-level* `None` contract of the canonical openers, and the #81
//! re-export firewall (a real-data source builds from `aura-ingest` alone). Every
//! item here is reached through `aura_ingest` ONLY — this test never names the
//! external `data_server` crate, which is the firewall it pins.
//!
//! Unlike the gated GER40 tests (which skip without a local Pepperstone archive),
//! these run everywhere: they drive a `DataServer` over a guaranteed-EMPTY base
//! path, so no archived file overlaps any window and the openers take their
//! documented "no data source present" branch deterministically — same input,
//! same `None`, every run, on any machine.
use std::sync::Arc;
// Firewall (#81): the ENTIRE source-open stack is imported from `aura_ingest`.
// `DataServer` / `DEFAULT_DATA_PATH` are re-exports; if a future change drops
// the re-export, this `use` stops compiling — that is the test.
use aura_core::Timestamp;
use aura_ingest::{
default_data_server, open_ohlc, DataServer, M1Field, M1FieldSource, DEFAULT_DATA_PATH,
};
/// A `DataServer` over a base path that is guaranteed not to exist, so its symbol
/// index is empty on every machine (data-server's directory scan returns `Err`
/// for an absent path and yields no symbols). Gives a deterministic "no archive"
/// server with no filesystem writes and no dependence on local data.
fn empty_server() -> Arc<DataServer> {
// A path component that cannot exist as a real archive directory; uniqueness
// is irrelevant (an absent path scans empty regardless), but keep it obviously
// synthetic so an accidental match is impossible.
let absent = std::env::temp_dir().join("aura-ingest-no-such-archive-DO-NOT-CREATE");
Arc::new(DataServer::new(absent))
}
/// Property: `open_ohlc` returns the *file-level* `None` (the "no data source to
/// read from" branch) when no archived file overlaps the window — distinct from
/// the "overlaps a file but holds zero bars" case, which would return
/// `Some(empty)`. Pins the documented Option semantics of the canonical OHLC
/// bundle opener (#80/#92) so a future refactor cannot silently turn "no source"
/// into a panic or an empty `Some`.
#[test]
fn open_ohlc_yields_file_level_none_when_no_archive_overlaps() {
let server = empty_server();
let from = Timestamp(0);
let to = Timestamp(1_000_000_000_000_000_000); // ~2001, well-formed epoch-ns window
assert!(
open_ohlc(&server, "GER40", from, to).is_none(),
"open_ohlc over an empty archive must return the file-level None (no source present)",
);
}
/// Property: `M1FieldSource::open_window` — the engine-side epoch-ns `Timestamp`
/// mirror of the ms-path `open` — threads its `Timestamp` bounds through the
/// seam-private ms<->ns crossing and propagates data-server's file-level `None`
/// for an absent symbol, without panicking on the ns->ms divide. Pins that the
/// Timestamp opener is a faithful, total mirror of `open` on the "no source"
/// branch (the C3 one-crossing seam, #80).
#[test]
fn open_window_propagates_file_level_none_for_absent_symbol() {
let server = empty_server();
let from = Timestamp(0);
let to = Timestamp(1_000_000_000_000_000_000);
assert!(
M1FieldSource::open_window(&server, "GER40", Some(from), Some(to), M1Field::Close)
.is_none(),
"open_window over an empty archive must propagate the file-level None",
);
// An open-ended window (None bounds) takes the same no-source branch — the
// ns->ms mapping is a no-op on None, so the openness is preserved.
assert!(
M1FieldSource::open_window(&server, "GER40", None, None, M1Field::Close).is_none(),
"open-ended open_window over an empty archive must also propagate None",
);
}
/// Property: the #81 source-open surface is reachable from `aura_ingest` alone —
/// `default_data_server()` constructs the canonical-path server and the
/// re-exported `DEFAULT_DATA_PATH` is the path it points at, with the external
/// `data_server` crate never named by a consumer. Observable: the convenience
/// constructor agrees with constructing `DataServer::new(DEFAULT_DATA_PATH)` by
/// hand on the symbol-presence query (both report the same absence of a synthetic
/// symbol that no real archive can hold), so the convenience is a faithful alias.
#[test]
fn default_data_server_builds_from_ingest_alone_over_canonical_path() {
let convenience = default_data_server();
// The re-export is reachable and is what the convenience constructor uses.
let by_hand = Arc::new(DataServer::new(DEFAULT_DATA_PATH));
// A symbol no real Pepperstone archive holds: both servers agree it is absent,
// independent of whether the local archive exists — so the convenience
// constructor is a faithful alias for `DataServer::new(DEFAULT_DATA_PATH)`,
// and the whole stack was built without naming `data_server`.
const SYNTHETIC_ABSENT: &str = "AURA-INGEST-SYNTHETIC-NONEXISTENT-SYMBOL";
assert_eq!(
convenience.has_symbol(SYNTHETIC_ABSENT),
by_hand.has_symbol(SYNTHETIC_ABSENT),
"default_data_server() must agree with DataServer::new(DEFAULT_DATA_PATH)",
);
assert!(
!convenience.has_symbol(SYNTHETIC_ABSENT),
"a synthetic symbol no archive holds must be absent",
);
}