mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
test: add golden output parity gate and live A/B compare
Phase 0 of docs/DESIGN.md, before any refactor code: - tests/golden.rs runs every case in tests/golden/cases.toml (126 outputs across help, completions, config, cache, all stocks commands on MSN and Yahoo mocks, error paths, and ownership queries on a seeded fixture DB) and compares exit code, stdout, and stderr byte for byte. Regenerate only with UPDATE_GOLDEN=1 for a reviewed output change. - scripts/live-smoke.sh --compare <old-idx> runs each live case with both binaries and fails on differing exit codes, added/missing/retyped JSON keys, or changed values for slow-moving data. - Exclude golden files from the whitespace/EOF pre-commit fixers, which would otherwise rewrite pinned help output. Both gates were fault-injected: a one-word table change fails exactly the three history goldens, and a binary dropping profile.long_name fails only the profile live comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
29796f0f9d
commit
fe627496dd
133 changed files with 6033 additions and 23 deletions
|
|
@ -1,8 +1,11 @@
|
|||
repos:
|
||||
- repo: builtin
|
||||
hooks:
|
||||
# Golden files pin exact CLI output, including trailing spaces.
|
||||
- id: trailing-whitespace
|
||||
exclude: ^tests/golden/.*\.golden$
|
||||
- id: end-of-file-fixer
|
||||
exclude: ^tests/golden/.*\.golden$
|
||||
- id: check-merge-conflict
|
||||
|
||||
- repo: local
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ src/
|
|||
├── ownership/ xlsx + pdf parsers, archive (ZIP/TXT), remote discovery,
|
||||
│ snapshot sync, db (SQLite/FTS5), entities, search, graph
|
||||
└── output/ table.rs, json.rs
|
||||
tests/cli.rs CLI integration tests; fixtures in tests/fixtures/
|
||||
tests/ cli.rs (behavior), golden.rs + golden/ (output parity), fixtures/
|
||||
scripts/ smoke, installer, and ownership snapshot publisher
|
||||
```
|
||||
|
||||
|
|
@ -42,5 +42,5 @@ Docs:
|
|||
```bash
|
||||
nix flake check -L # canonical gate (fmt, clippy, tests, package)
|
||||
nix develop --command cargo test <name> # focused loop
|
||||
scripts/live-smoke.sh # opt-in live provider drift check
|
||||
scripts/live-smoke.sh --compare <old> # live drift check, A/B against an older build
|
||||
```
|
||||
|
|
|
|||
|
|
@ -162,7 +162,8 @@ is another 20-30% smaller than pretty JSON.
|
|||
- stdout carries only data; diagnostics and warnings go to stderr. Already true.
|
||||
- Errors in `json`/`jsonl` mode are one JSON object on stderr with stdout empty.
|
||||
Already true.
|
||||
- No ANSI color when stdout is not a terminal.
|
||||
- No ANSI color when stdout is not a terminal. Today some table headers emit
|
||||
bold codes even when piped.
|
||||
- Commands that take symbols accept `-` to read them from stdin, one per line or
|
||||
whitespace-separated:
|
||||
`idx stocks screen --filter low-pe | jq -r '.[].symbol' | idx stocks compare -`
|
||||
|
|
@ -264,14 +265,16 @@ Before any refactor code lands, capture the current CLI as golden files:
|
|||
fixture-built ownership DB
|
||||
- the documented error cases (invalid input, unsupported provider, offline
|
||||
without cache, empty ownership DB)
|
||||
- stored as `tests/golden/<case>.{stdout,stderr,exit}` and compared byte for
|
||||
byte by one data-driven test in `tests/golden.rs`
|
||||
- cases listed in `tests/golden/cases.toml`; each result stored as
|
||||
`tests/golden/<case>.<format>.golden` (exit code, stdout, stderr) and
|
||||
compared byte for byte by one data-driven test in `tests/golden.rs`
|
||||
|
||||
Regenerating goldens (`UPDATE_GOLDEN=1`) is only allowed for an intentional,
|
||||
reviewed output change, and the diff must be in the same commit with the reason
|
||||
in the message. Phases 1-3 must not change any golden file. Phase 0 changes only
|
||||
what it documents: default format resolution, JSON whitespace when piped, and
|
||||
exit codes; JSON content and table bytes stay identical.
|
||||
in the message. Phases 2-4 must not change any golden file. Phase 1 changes only
|
||||
what it documents: default format resolution, JSON whitespace when piped, ANSI
|
||||
color when piped (table output emits bold headers into pipes today), and exit
|
||||
codes; JSON content and table text stay identical.
|
||||
|
||||
`tests/cli.rs` stays the behavioral suite and must keep passing unchanged.
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ offline with pinned tools:
|
|||
| Check | Covers |
|
||||
| --- | --- |
|
||||
| `fmt`, `clippy` | formatting and lints (`--all-targets`) |
|
||||
| `test` | unit/module tests and `tests/cli.rs` CLI integration tests |
|
||||
| `test` | unit/module tests, `tests/cli.rs` CLI integration tests, and `tests/golden.rs` output parity |
|
||||
| `default`, `package` | release build and `cargo package` verification |
|
||||
| `packaged-cli` | the wrapped Nix binary starts, emits JSON, and generates completions |
|
||||
| `ownership-publisher`, `publisher-test` | packaged publisher entry point and its regression scenarios |
|
||||
|
|
@ -19,6 +19,12 @@ fixture-backed mock providers (`IDX_USE_MOCK_PROVIDER=1`) and covers output
|
|||
contracts, cache/offline transitions, provider routing, error codes, ownership
|
||||
imports and queries, and snapshot sync.
|
||||
|
||||
`tests/golden.rs` pins the exact output (exit code, stdout, stderr) of every
|
||||
command listed in `tests/golden/cases.toml`, in table and JSON. A mismatch means
|
||||
user-visible output changed. Regenerate with
|
||||
`UPDATE_GOLDEN=1 cargo test --test golden` only when the change is intended,
|
||||
and commit the golden diff with the reason.
|
||||
|
||||
For a focused loop, run one check or filter Cargo tests:
|
||||
|
||||
```bash
|
||||
|
|
@ -39,6 +45,7 @@ validates each JSON output shape. It is opt-in and never part of the gate.
|
|||
nix develop --command scripts/live-smoke.sh # stocks + ownership
|
||||
nix develop --command scripts/live-smoke.sh --group stocks --symbol BBRI
|
||||
nix develop --command scripts/live-smoke.sh --bin ./result/bin/idx
|
||||
nix develop --command scripts/live-smoke.sh --compare ./old/bin/idx # A/B against an older build
|
||||
scripts/live-smoke.sh --dry-run
|
||||
```
|
||||
|
||||
|
|
@ -48,6 +55,12 @@ scripts/live-smoke.sh --dry-run
|
|||
DB and lists `releases`. IDX sits behind Cloudflare and returns `403` to many
|
||||
datacenter IPs, including GitHub-hosted runners; run it from a network IDX accepts.
|
||||
|
||||
`--compare <old-idx>` runs every case with both binaries back to back, each
|
||||
with its own config and cache, and fails on a different exit code, any added,
|
||||
missing, or retyped JSON key, or changed values for `profile`, `financials`,
|
||||
`earnings`, and ownership results. Build the old side with, for example,
|
||||
`nix build "git+file://$PWD?ref=main" -o old`.
|
||||
|
||||
Each run isolates config, cache, and data under `tmp/live-smoke/<timestamp>/`
|
||||
and keeps per-case `.stdout`, `.stderr`, and `.validation` logs there.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
#!/usr/bin/env bash
|
||||
# Live provider drift check: runs shipped commands against real MSN, Yahoo,
|
||||
# and IDX endpoints and validates the JSON output shape. Deterministic CLI
|
||||
# behavior is covered by tests/cli.rs; this only answers "do the upstreams
|
||||
# still look the way our parsers expect?"
|
||||
# behavior is covered by tests/cli.rs and tests/golden.rs; this answers "do the
|
||||
# upstreams still look the way our parsers expect?"
|
||||
#
|
||||
# With --compare <old-idx>, every case also runs the old binary back to back
|
||||
# and fails on any difference in exit code or JSON structure, and on any value
|
||||
# difference for slow-moving data (see "Live A/B check" in docs/DESIGN.md).
|
||||
|
||||
set -u
|
||||
set -o pipefail
|
||||
|
|
@ -10,6 +14,7 @@ set -o pipefail
|
|||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
SYMBOL="BBCA"
|
||||
BIN_PATH=""
|
||||
COMPARE_BIN=""
|
||||
DRY_RUN=0
|
||||
declare -a GROUPS_SELECTED=()
|
||||
declare -a FAILURES=()
|
||||
|
|
@ -24,6 +29,7 @@ Options:
|
|||
--group <stocks|ownership> Restrict to a group. Repeatable. Default: both
|
||||
--symbol <ticker> Primary ticker. Default: BBCA
|
||||
--bin <path> Use an existing idx binary (skips cargo build)
|
||||
--compare <old-idx> Also run an older idx binary and diff the results
|
||||
--dry-run Print cases without running them
|
||||
--help Show this help
|
||||
EOF
|
||||
|
|
@ -34,6 +40,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--group) GROUPS_SELECTED+=("${2:-}"); shift 2 ;;
|
||||
--symbol) SYMBOL="${2:-}"; shift 2 ;;
|
||||
--bin) BIN_PATH="${2:-}"; shift 2 ;;
|
||||
--compare) COMPARE_BIN="${2:-}"; shift 2 ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--help|-h) usage; exit 0 ;;
|
||||
*) echo "unknown argument: $1" >&2; usage >&2; exit 2 ;;
|
||||
|
|
@ -58,14 +65,36 @@ selected() {
|
|||
|
||||
WORKDIR="$ROOT_DIR/tmp/live-smoke/$(date +%Y%m%d-%H%M%S)"
|
||||
LOG_DIR="$WORKDIR/logs"
|
||||
BASE_ENV=(
|
||||
"XDG_CONFIG_HOME=$WORKDIR/config"
|
||||
"XDG_CACHE_HOME=$WORKDIR/cache"
|
||||
"XDG_DATA_HOME=$WORKDIR/data"
|
||||
"IDX_PROVIDER=msn"
|
||||
"IDX_HISTORY_PROVIDER=auto"
|
||||
"IDX_NO_COLOR=1"
|
||||
)
|
||||
COMMON_ENV=("IDX_PROVIDER=msn" "IDX_HISTORY_PROVIDER=auto" "IDX_NO_COLOR=1")
|
||||
|
||||
# Each binary gets its own config, cache, and data so both really fetch.
|
||||
side_env() {
|
||||
local home="$WORKDIR/$1"
|
||||
printf '%s\n' "XDG_CONFIG_HOME=$home/config" "XDG_CACHE_HOME=$home/cache" \
|
||||
"XDG_DATA_HOME=$home/data" "${COMMON_ENV[@]}"
|
||||
}
|
||||
|
||||
# Values of these commands should not move between two back-to-back runs.
|
||||
stable_values() {
|
||||
case "$1" in
|
||||
"stocks profile "*|"stocks financials "*|"stocks earnings "*|"ownership ticker "*|"ownership releases") return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Key paths (array indices collapsed) that disappeared or appeared, and keys
|
||||
# whose non-null type changed. Empty lists mean the structure matches.
|
||||
# shellcheck disable=SC2016
|
||||
SHAPE_DIFF='
|
||||
def norm: map(if type == "number" then "*" else . end) | join(".");
|
||||
def keyset: [paths | norm] | unique;
|
||||
def types: [paths(scalars) as $p | select(getpath($p) != null)
|
||||
| {k: ($p | norm), t: (getpath($p) | type)}]
|
||||
| group_by(.k) | map({key: .[0].k, value: (map(.t) | unique)}) | from_entries;
|
||||
($old[0] | keyset) as $ko | ($new[0] | keyset) as $kn
|
||||
| ($old[0] | types) as $to | ($new[0] | types) as $tn
|
||||
| {missing: ($ko - $kn), added: ($kn - $ko),
|
||||
type_changed: [$to | keys[] as $k | select($tn[$k] != null and $tn[$k] != $to[$k]) | $k]}'
|
||||
|
||||
# jq filter describing the JSON shape each command must keep.
|
||||
contract() {
|
||||
|
|
@ -103,8 +132,16 @@ contract() {
|
|||
esac
|
||||
}
|
||||
|
||||
run_bin() {
|
||||
local side="$1" bin="$2" out="$3"
|
||||
shift 3
|
||||
local -a envs
|
||||
mapfile -t envs < <(side_env "$side")
|
||||
env "${envs[@]}" "$bin" -o json "$@" >"$out.stdout" 2>"$out.stderr"
|
||||
}
|
||||
|
||||
run_case() {
|
||||
local name="$1" cmd="$2" filter log
|
||||
local name="$1" cmd="$2" filter log status old_status diff
|
||||
local -a args
|
||||
TOTAL=$((TOTAL + 1))
|
||||
printf '[%02d] %s\n' "$TOTAL" "$name"
|
||||
|
|
@ -115,8 +152,19 @@ run_case() {
|
|||
|
||||
log="$LOG_DIR/$(printf '%02d' "$TOTAL")_$name"
|
||||
read -r -a args <<< "$cmd"
|
||||
if ! env "${BASE_ENV[@]}" "$BIN_PATH" -o json "${args[@]}" >"$log.stdout" 2>"$log.stderr"; then
|
||||
FAILURES+=("$name: non-zero exit (see $log.stderr)")
|
||||
run_bin new "$BIN_PATH" "$log" "${args[@]}"
|
||||
status=$?
|
||||
if [[ -n "$COMPARE_BIN" ]]; then
|
||||
run_bin old "$COMPARE_BIN" "$log.old" "${args[@]}"
|
||||
old_status=$?
|
||||
if (( status != old_status )); then
|
||||
FAILURES+=("$name: exit $status, old binary exited $old_status (see $log.stderr, $log.old.stderr)")
|
||||
echo " FAIL exit differs from old binary"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
if (( status != 0 )); then
|
||||
FAILURES+=("$name: exit $status (see $log.stderr)")
|
||||
echo " FAIL exit"
|
||||
return
|
||||
fi
|
||||
|
|
@ -126,6 +174,22 @@ run_case() {
|
|||
echo " FAIL shape"
|
||||
return
|
||||
fi
|
||||
if [[ -n "$COMPARE_BIN" ]]; then
|
||||
diff="$(jq -n --slurpfile old "$log.old.stdout" --slurpfile new "$log.stdout" "$SHAPE_DIFF")"
|
||||
if [[ "$(jq -c '[.[] | length] | add' <<< "$diff")" != 0 ]]; then
|
||||
printf '%s\n' "$diff" >"$log.diff"
|
||||
FAILURES+=("$name: JSON structure differs from old binary (see $log.diff)")
|
||||
echo " FAIL structure differs"
|
||||
return
|
||||
fi
|
||||
if stable_values "$cmd" && ! cmp -s \
|
||||
<(jq -S 'del(.. | .imported_at?)' "$log.old.stdout") \
|
||||
<(jq -S 'del(.. | .imported_at?)' "$log.stdout"); then
|
||||
FAILURES+=("$name: values differ from old binary (diff $log.old.stdout $log.stdout)")
|
||||
echo " FAIL values differ"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
PASSED=$((PASSED + 1))
|
||||
echo " PASS"
|
||||
}
|
||||
|
|
@ -137,7 +201,10 @@ if (( ! DRY_RUN )); then
|
|||
BIN_PATH="$ROOT_DIR/target/debug/idx"
|
||||
(cd "$ROOT_DIR" && cargo build --quiet --bin idx) || { echo "cargo build failed" >&2; exit 1; }
|
||||
fi
|
||||
env "${BASE_ENV[@]}" "$BIN_PATH" config set ownership.db_path "$WORKDIR/ownership.db" >/dev/null
|
||||
if [[ -n "$COMPARE_BIN" && ! -x "$COMPARE_BIN" ]]; then
|
||||
echo "--compare binary not found or not executable: $COMPARE_BIN" >&2
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
if selected stocks; then
|
||||
|
|
@ -158,10 +225,12 @@ if selected ownership; then
|
|||
url="$(jq -r '.[0].pdf_url' "$LOG_DIR/$(printf '%02d' "$TOTAL")_discover.stdout")"
|
||||
run_case import "ownership import --url $url"
|
||||
run_case releases "ownership releases"
|
||||
run_case ticker "ownership ticker $SYMBOL"
|
||||
fi
|
||||
fi
|
||||
|
||||
(( DRY_RUN )) && { echo "dry-run: $TOTAL case(s)"; exit 0; }
|
||||
[[ -n "$COMPARE_BIN" ]] && printf '\ncompared against: %s\n' "$COMPARE_BIN"
|
||||
printf '\nsummary: %d passed, %d failed\nlogs: %s\n' "$PASSED" "$((TOTAL - PASSED))" "$LOG_DIR"
|
||||
if (( ${#FAILURES[@]} > 0 )); then
|
||||
printf ' - %s\n' "${FAILURES[@]}"
|
||||
|
|
|
|||
208
tests/golden.rs
Normal file
208
tests/golden.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
//! Golden output tests: byte-for-byte parity of CLI stdout, stderr, and exit
|
||||
//! code for every shipped command (see "Parity and Verification" in
|
||||
//! docs/DESIGN.md).
|
||||
//!
|
||||
//! Cases live in `tests/golden/cases.toml`; expected output in
|
||||
//! `tests/golden/<case>.golden`. Regenerate only for an intentional, reviewed
|
||||
//! output change, and commit the diff with the reason:
|
||||
//!
|
||||
//! UPDATE_GOLDEN=1 cargo test --test golden
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
const IDX: &str = env!("CARGO_BIN_EXE_idx");
|
||||
const REPO: &str = env!("CARGO_MANIFEST_DIR");
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CaseFile {
|
||||
case: Vec<Case>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Case {
|
||||
name: String,
|
||||
args: Vec<String>,
|
||||
/// Output formats to expand the case into (`-o <format>` is prepended and
|
||||
/// `.<format>` appended to the name). Empty means run the args as given.
|
||||
#[serde(default = "default_formats")]
|
||||
formats: Vec<String>,
|
||||
#[serde(default)]
|
||||
env: BTreeMap<String, String>,
|
||||
/// Seed the fixture ownership DB before running.
|
||||
#[serde(default)]
|
||||
ownership: bool,
|
||||
/// Commands run first in the same environment; their output is ignored.
|
||||
#[serde(default)]
|
||||
before: Vec<Vec<String>>,
|
||||
}
|
||||
|
||||
fn default_formats() -> Vec<String> {
|
||||
vec!["table".into(), "json".into()]
|
||||
}
|
||||
|
||||
fn golden_dir() -> PathBuf {
|
||||
Path::new(REPO).join("tests").join("golden")
|
||||
}
|
||||
|
||||
fn work_dir() -> PathBuf {
|
||||
std::env::temp_dir().join(format!("idx-golden-{}", std::process::id()))
|
||||
}
|
||||
|
||||
/// Runs idx with an isolated home under `root` and only the given IDX_* env.
|
||||
fn idx(root: &Path, args: &[String], env: &BTreeMap<String, String>) -> std::process::Output {
|
||||
let mut cmd = Command::new(IDX);
|
||||
cmd.current_dir(REPO).args(args);
|
||||
for (key, _) in std::env::vars_os() {
|
||||
if key.to_string_lossy().starts_with("IDX_") || key == "NO_COLOR" {
|
||||
cmd.env_remove(key);
|
||||
}
|
||||
}
|
||||
cmd.env("HOME", root)
|
||||
.env("XDG_CONFIG_HOME", root.join("config"))
|
||||
.env("XDG_CACHE_HOME", root.join("cache"))
|
||||
.env("XDG_DATA_HOME", root.join("data"))
|
||||
.env("IDX_USE_MOCK_PROVIDER", "1")
|
||||
.envs(env);
|
||||
cmd.output().expect("run idx")
|
||||
}
|
||||
|
||||
/// Fixture ownership DB: the above-1% XLSX (2026-08-31) plus two KSEI archive
|
||||
/// months (2026-01-30, 2026-02-27), with wall-clock columns pinned.
|
||||
fn seeded_ownership_db() -> &'static Path {
|
||||
static DB: OnceLock<PathBuf> = OnceLock::new();
|
||||
DB.get_or_init(|| {
|
||||
let root = work_dir().join("seed");
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
fs::create_dir_all(&root).expect("create seed dir");
|
||||
// The importer derives the release from the IDX file name.
|
||||
let xlsx = root.join("peng-2026-08-00017-satu-persen.xlsx");
|
||||
fs::copy(
|
||||
Path::new(REPO).join("tests/fixtures/ksei_above1_20260831_excerpt.xlsx"),
|
||||
&xlsx,
|
||||
)
|
||||
.expect("copy xlsx fixture");
|
||||
let inputs = [
|
||||
xlsx,
|
||||
Path::new(REPO).join("tests/fixtures/ksei_balancepos_20260130_excerpt.txt"),
|
||||
Path::new(REPO).join("tests/fixtures/ksei_balancepos_20260227_excerpt.txt"),
|
||||
];
|
||||
for input in inputs {
|
||||
let args = ["ownership", "import", "--file", input.to_str().unwrap()]
|
||||
.map(String::from)
|
||||
.to_vec();
|
||||
let out = idx(&root, &args, &BTreeMap::new());
|
||||
assert!(out.status.success(), "seed import failed: {out:?}");
|
||||
}
|
||||
let db = root.join("data/idx/ownership.db");
|
||||
let conn = rusqlite::Connection::open(&db).expect("open seed db");
|
||||
conn.execute_batch(
|
||||
"UPDATE ownership_releases SET imported_at = 0;
|
||||
UPDATE entities SET created_at = 0, updated_at = 0;
|
||||
UPDATE bing_holdings SET fetched_at = 0;",
|
||||
)
|
||||
.expect("pin timestamps");
|
||||
db
|
||||
})
|
||||
}
|
||||
|
||||
fn render(root: &Path, out: &std::process::Output) -> String {
|
||||
let normalize = |bytes: &[u8]| {
|
||||
String::from_utf8_lossy(bytes)
|
||||
.replace(root.to_str().unwrap(), "<ROOT>")
|
||||
.replace(REPO, "<REPO>")
|
||||
};
|
||||
format!(
|
||||
"exit: {}\n--- stdout\n{}--- stderr\n{}",
|
||||
out.status.code().map_or("signal".into(), |c| c.to_string()),
|
||||
normalize(&out.stdout),
|
||||
normalize(&out.stderr),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cli_output_matches_golden_files() {
|
||||
let raw = fs::read_to_string(golden_dir().join("cases.toml")).expect("read cases.toml");
|
||||
let cases: CaseFile = toml::from_str(&raw).expect("parse cases.toml");
|
||||
let update = std::env::var_os("UPDATE_GOLDEN").is_some();
|
||||
let mut seen = BTreeSet::new();
|
||||
let mut failures = Vec::new();
|
||||
|
||||
for case in &cases.case {
|
||||
let runs: Vec<(String, Vec<String>)> = if case.formats.is_empty() {
|
||||
vec![(case.name.clone(), case.args.clone())]
|
||||
} else {
|
||||
case.formats
|
||||
.iter()
|
||||
.map(|format| {
|
||||
let mut args = vec!["-o".to_string(), format.clone()];
|
||||
args.extend(case.args.iter().cloned());
|
||||
(format!("{}.{format}", case.name), args)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
for (name, args) in runs {
|
||||
assert!(seen.insert(name.clone()), "duplicate golden case {name}");
|
||||
let root = work_dir().join(&name);
|
||||
let _ = fs::remove_dir_all(&root);
|
||||
fs::create_dir_all(&root).expect("create case dir");
|
||||
if case.ownership {
|
||||
let target = root.join("data/idx/ownership.db");
|
||||
fs::create_dir_all(target.parent().unwrap()).expect("create data dir");
|
||||
fs::copy(seeded_ownership_db(), &target).expect("copy seeded db");
|
||||
}
|
||||
for before in &case.before {
|
||||
idx(&root, before, &case.env);
|
||||
}
|
||||
|
||||
let actual = render(&root, &idx(&root, &args, &case.env));
|
||||
let path = golden_dir().join(format!("{name}.golden"));
|
||||
if update {
|
||||
fs::write(&path, &actual).expect("write golden");
|
||||
} else {
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(expected) if expected == actual => {}
|
||||
Ok(expected) => failures.push(format!(
|
||||
"{name}: output differs\n--- expected\n{expected}\n--- actual\n{actual}"
|
||||
)),
|
||||
Err(_) => failures.push(format!("{name}: missing {}", path.display())),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(golden_dir()).expect("read golden dir") {
|
||||
let path = entry.expect("dir entry").path();
|
||||
let Some(stem) = path
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.strip_suffix(".golden")
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
if !seen.contains(stem) {
|
||||
if update {
|
||||
fs::remove_file(&path).expect("remove orphan golden");
|
||||
} else {
|
||||
failures.push(format!("{stem}: golden file has no case in cases.toml"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(work_dir());
|
||||
assert!(
|
||||
failures.is_empty(),
|
||||
"{} golden mismatch(es); if the change is intentional, rerun with UPDATE_GOLDEN=1 \
|
||||
and commit the diff with the reason\n\n{}",
|
||||
failures.len(),
|
||||
failures.join("\n\n")
|
||||
);
|
||||
}
|
||||
7
tests/golden/cache-clear.json.golden
Normal file
7
tests/golden/cache-clear.json.golden
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"failed": [],
|
||||
"removed": 0
|
||||
}
|
||||
--- stderr
|
||||
4
tests/golden/cache-clear.table.golden
Normal file
4
tests/golden/cache-clear.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
cleared 0 files
|
||||
--- stderr
|
||||
10
tests/golden/cache-info-empty.json.golden
Normal file
10
tests/golden/cache-info-empty.json.golden
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"files": 0,
|
||||
"newest": null,
|
||||
"oldest": null,
|
||||
"path": "<ROOT>/cache/idx",
|
||||
"size_bytes": 0
|
||||
}
|
||||
--- stderr
|
||||
8
tests/golden/cache-info-empty.table.golden
Normal file
8
tests/golden/cache-info-empty.table.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
path: <ROOT>/cache/idx
|
||||
files: 0
|
||||
size_bytes: 0
|
||||
oldest: -
|
||||
newest: -
|
||||
--- stderr
|
||||
327
tests/golden/cases.toml
Normal file
327
tests/golden/cases.toml
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
# Golden output cases for tests/golden.rs.
|
||||
#
|
||||
# Each case runs once per entry in `formats` (default: table and json) with
|
||||
# `-o <format>` prepended; `formats = []` runs the args as given. Every run uses
|
||||
# the mock provider (IDX_USE_MOCK_PROVIDER=1) and an isolated home directory.
|
||||
# `ownership = true` seeds the fixture DB; `before` runs setup commands first.
|
||||
#
|
||||
# `idx version` is excluded on purpose: its output changes with every release.
|
||||
|
||||
# --- help and shell completions ---------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "help-root"
|
||||
args = ["--help"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "help-stocks"
|
||||
args = ["stocks", "--help"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "help-ownership"
|
||||
args = ["ownership", "--help"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "help-config"
|
||||
args = ["config", "--help"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "help-cache"
|
||||
args = ["cache", "--help"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "completions-bash"
|
||||
args = ["completions", "bash"]
|
||||
formats = []
|
||||
|
||||
# --- config and cache ---------------------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "config-path"
|
||||
args = ["config", "path"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "config-init"
|
||||
args = ["config", "init"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "config-get"
|
||||
args = ["config", "get", "general.provider"]
|
||||
formats = []
|
||||
before = [["config", "init"]]
|
||||
|
||||
[[case]]
|
||||
name = "config-set"
|
||||
args = ["config", "set", "general.provider", "yahoo"]
|
||||
formats = []
|
||||
|
||||
[[case]]
|
||||
name = "cache-info-empty"
|
||||
args = ["cache", "info"]
|
||||
|
||||
[[case]]
|
||||
name = "cache-clear"
|
||||
args = ["cache", "clear"]
|
||||
|
||||
# --- stocks: MSN (default provider) --------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "stocks-quote"
|
||||
args = ["stocks", "quote", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-quote-multi"
|
||||
args = ["stocks", "quote", "BBCA,BBRI"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-history"
|
||||
args = ["stocks", "history", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-history-msn"
|
||||
args = ["stocks", "history", "BBCA", "--history-provider", "msn"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-technical"
|
||||
args = ["stocks", "technical", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-growth"
|
||||
args = ["stocks", "growth", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-valuation"
|
||||
args = ["stocks", "valuation", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-valuation-nonfinite"
|
||||
args = ["stocks", "valuation", "BBCA"]
|
||||
env = { IDX_MOCK_MSN_KEYRATIOS_FIXTURE = "tests/fixtures/msn_keyratios_infinity.json" }
|
||||
|
||||
[[case]]
|
||||
name = "stocks-risk"
|
||||
args = ["stocks", "risk", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-fundamental"
|
||||
args = ["stocks", "fundamental", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-compare"
|
||||
args = ["stocks", "compare", "BBCA", "BBRI"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-profile"
|
||||
args = ["stocks", "profile", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-financials"
|
||||
args = ["stocks", "financials", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-financials-income"
|
||||
args = ["stocks", "financials", "BBCA", "--statement", "income"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-earnings"
|
||||
args = ["stocks", "earnings", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-earnings-history-quarterly"
|
||||
args = ["stocks", "earnings", "BBCA", "--history", "--quarterly"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-earnings-forecast-annual"
|
||||
args = ["stocks", "earnings", "BBCA", "--forecast", "--annual"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-sentiment"
|
||||
args = ["stocks", "sentiment", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-insights"
|
||||
args = ["stocks", "insights", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-news"
|
||||
args = ["stocks", "news", "BBCA", "--limit", "5"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-screen"
|
||||
args = ["stocks", "screen"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-screen-worst"
|
||||
args = ["stocks", "screen", "--filter", "worst-performers", "--limit", "5"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-screen-high-volume"
|
||||
args = ["stocks", "screen", "--filter", "high-volume"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-screen-large-cap"
|
||||
args = ["stocks", "screen", "--filter", "large-cap"]
|
||||
|
||||
[[case]]
|
||||
name = "stocks-quote-offline-warm"
|
||||
args = ["--offline", "stocks", "quote", "BBCA"]
|
||||
before = [["stocks", "quote", "BBCA"]]
|
||||
|
||||
# --- stocks: Yahoo provider -----------------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "yahoo-quote"
|
||||
args = ["stocks", "quote", "BBCA"]
|
||||
env = { IDX_PROVIDER = "yahoo" }
|
||||
|
||||
[[case]]
|
||||
name = "yahoo-history"
|
||||
args = ["stocks", "history", "BBCA"]
|
||||
env = { IDX_PROVIDER = "yahoo" }
|
||||
|
||||
[[case]]
|
||||
name = "yahoo-fundamental"
|
||||
args = ["stocks", "fundamental", "BBCA"]
|
||||
env = { IDX_PROVIDER = "yahoo" }
|
||||
|
||||
# --- errors ---------------------------------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "error-usage-missing-symbol"
|
||||
args = ["stocks", "quote"]
|
||||
|
||||
[[case]]
|
||||
name = "error-invalid-provider"
|
||||
args = ["stocks", "quote", "BBCA"]
|
||||
env = { IDX_PROVIDER = "bogus" }
|
||||
|
||||
[[case]]
|
||||
name = "error-yahoo-profile-unsupported"
|
||||
args = ["stocks", "profile", "BBCA"]
|
||||
env = { IDX_PROVIDER = "yahoo" }
|
||||
|
||||
[[case]]
|
||||
name = "error-industry-only-fundamentals"
|
||||
args = ["stocks", "valuation", "BBCA"]
|
||||
env = { IDX_MOCK_MSN_KEYRATIOS_FIXTURE = "tests/fixtures/msn_keyratios_industry_only.json" }
|
||||
|
||||
[[case]]
|
||||
name = "error-screen-region"
|
||||
args = ["stocks", "screen", "--region", "eu"]
|
||||
|
||||
[[case]]
|
||||
name = "error-offline-no-cache-flags"
|
||||
args = ["--offline", "--no-cache", "stocks", "quote", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "error-offline-cache-miss"
|
||||
args = ["--offline", "stocks", "quote", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "error-provider-failure"
|
||||
args = ["stocks", "quote", "BBCA"]
|
||||
env = { IDX_MOCK_ERROR = "1" }
|
||||
|
||||
[[case]]
|
||||
name = "error-compare-total-failure"
|
||||
args = ["stocks", "compare", "BBCA", "BBRI"]
|
||||
env = { IDX_MOCK_ERROR = "1" }
|
||||
|
||||
[[case]]
|
||||
name = "error-fetch-bing-unsupported"
|
||||
args = ["ownership", "import", "--fetch-bing", "BBCA"]
|
||||
|
||||
# --- ownership --------------------------------------------------------------------
|
||||
|
||||
[[case]]
|
||||
name = "ownership-releases-empty"
|
||||
args = ["ownership", "releases"]
|
||||
|
||||
[[case]]
|
||||
name = "ownership-ticker-empty"
|
||||
args = ["ownership", "ticker", "BBCA"]
|
||||
|
||||
[[case]]
|
||||
name = "ownership-import-xlsx"
|
||||
args = ["ownership", "import", "--file", "tests/fixtures/ksei_above1_20260831_excerpt.xlsx"]
|
||||
|
||||
[[case]]
|
||||
name = "ownership-import-archive-txt"
|
||||
args = ["ownership", "import", "--file", "tests/fixtures/ksei_balancepos_20260227_excerpt.txt"]
|
||||
|
||||
[[case]]
|
||||
name = "ownership-releases"
|
||||
args = ["ownership", "releases"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-ticker"
|
||||
args = ["ownership", "ticker", "BBCA"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-ticker-ksei"
|
||||
args = ["ownership", "ticker", "BBCA", "--source", "ksei"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-entity"
|
||||
args = ["ownership", "entity", "ANTHONI SALIM"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-search"
|
||||
args = ["ownership", "search", "salim"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-cross-holders"
|
||||
args = ["ownership", "cross-holders", "--min-tickers", "1"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-concentration"
|
||||
args = ["ownership", "concentration"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-flow"
|
||||
args = ["ownership", "flow", "BBCA"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-changes"
|
||||
args = ["ownership", "changes", "--from", "2026-01-30", "--to", "2026-02-27"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-graph"
|
||||
args = ["ownership", "graph", "BBCA"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-graph-dot"
|
||||
args = ["ownership", "graph", "BBCA", "--format", "dot"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-resolve-unresolved"
|
||||
args = ["ownership", "resolve", "unresolved"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-resolve-map"
|
||||
args = ["ownership", "resolve", "map", "SALIM ANTHONI", "ANTHONI SALIM"]
|
||||
ownership = true
|
||||
|
||||
[[case]]
|
||||
name = "ownership-resolve-merge"
|
||||
args = ["ownership", "resolve", "merge", "1", "2"]
|
||||
ownership = true
|
||||
2851
tests/golden/completions-bash.golden
Normal file
2851
tests/golden/completions-bash.golden
Normal file
File diff suppressed because it is too large
Load diff
4
tests/golden/config-get.golden
Normal file
4
tests/golden/config-get.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
msn
|
||||
--- stderr
|
||||
4
tests/golden/config-init.golden
Normal file
4
tests/golden/config-init.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
<ROOT>/config/idx/config.toml
|
||||
--- stderr
|
||||
4
tests/golden/config-path.golden
Normal file
4
tests/golden/config-path.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
<ROOT>/config/idx/config.toml
|
||||
--- stderr
|
||||
4
tests/golden/config-set.golden
Normal file
4
tests/golden/config-set.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
ok
|
||||
--- stderr
|
||||
8
tests/golden/error-compare-total-failure.json.golden
Normal file
8
tests/golden/error-compare-total-failure.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "PROVIDERUNAVAILABLE",
|
||||
"error": true,
|
||||
"message": "provider unavailable"
|
||||
}
|
||||
4
tests/golden/error-compare-total-failure.table.golden
Normal file
4
tests/golden/error-compare-total-failure.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: provider unavailable
|
||||
8
tests/golden/error-fetch-bing-unsupported.json.golden
Normal file
8
tests/golden/error-fetch-bing-unsupported.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "UNSUPPORTED",
|
||||
"error": true,
|
||||
"message": "unsupported: --fetch-bing import is not implemented yet"
|
||||
}
|
||||
4
tests/golden/error-fetch-bing-unsupported.table.golden
Normal file
4
tests/golden/error-fetch-bing-unsupported.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: unsupported: --fetch-bing import is not implemented yet
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "PARSEERROR",
|
||||
"error": true,
|
||||
"message": "parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: parse error: unsupported: company fundamentals unavailable from MSN; industry fallback is disabled
|
||||
8
tests/golden/error-invalid-provider.json.golden
Normal file
8
tests/golden/error-invalid-provider.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "CONFIGERROR",
|
||||
"error": true,
|
||||
"message": "config error: invalid provider 'bogus' (expected yahoo or msn)"
|
||||
}
|
||||
4
tests/golden/error-invalid-provider.table.golden
Normal file
4
tests/golden/error-invalid-provider.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: config error: invalid provider 'bogus' (expected yahoo or msn)
|
||||
8
tests/golden/error-offline-cache-miss.json.golden
Normal file
8
tests/golden/error-offline-cache-miss.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "CACHEMISS",
|
||||
"error": true,
|
||||
"message": "cache miss: msn-quote/BBCA.JK"
|
||||
}
|
||||
4
tests/golden/error-offline-cache-miss.table.golden
Normal file
4
tests/golden/error-offline-cache-miss.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: cache miss: msn-quote/BBCA.JK
|
||||
8
tests/golden/error-offline-no-cache-flags.json.golden
Normal file
8
tests/golden/error-offline-no-cache-flags.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "INVALIDINPUT",
|
||||
"error": true,
|
||||
"message": "invalid input: cannot combine --offline with --no-cache"
|
||||
}
|
||||
4
tests/golden/error-offline-no-cache-flags.table.golden
Normal file
4
tests/golden/error-offline-no-cache-flags.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: invalid input: cannot combine --offline with --no-cache
|
||||
8
tests/golden/error-provider-failure.json.golden
Normal file
8
tests/golden/error-provider-failure.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "PROVIDERUNAVAILABLE",
|
||||
"error": true,
|
||||
"message": "provider unavailable"
|
||||
}
|
||||
4
tests/golden/error-provider-failure.table.golden
Normal file
4
tests/golden/error-provider-failure.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: provider unavailable
|
||||
8
tests/golden/error-screen-region.json.golden
Normal file
8
tests/golden/error-screen-region.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "INVALIDINPUT",
|
||||
"error": true,
|
||||
"message": "invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)"
|
||||
}
|
||||
4
tests/golden/error-screen-region.table.golden
Normal file
4
tests/golden/error-screen-region.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: invalid input: invalid screener region 'eu' (expected one of: id, us, sg, hk, jp)
|
||||
9
tests/golden/error-usage-missing-symbol.json.golden
Normal file
9
tests/golden/error-usage-missing-symbol.json.golden
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
exit: 2
|
||||
--- stdout
|
||||
--- stderr
|
||||
error: the following required arguments were not provided:
|
||||
<SYMBOLS>...
|
||||
|
||||
Usage: idx stocks quote <SYMBOLS>...
|
||||
|
||||
For more information, try '--help'.
|
||||
9
tests/golden/error-usage-missing-symbol.table.golden
Normal file
9
tests/golden/error-usage-missing-symbol.table.golden
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
exit: 2
|
||||
--- stdout
|
||||
--- stderr
|
||||
error: the following required arguments were not provided:
|
||||
<SYMBOLS>...
|
||||
|
||||
Usage: idx stocks quote <SYMBOLS>...
|
||||
|
||||
For more information, try '--help'.
|
||||
8
tests/golden/error-yahoo-profile-unsupported.json.golden
Normal file
8
tests/golden/error-yahoo-profile-unsupported.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "UNSUPPORTED",
|
||||
"error": true,
|
||||
"message": "unsupported: BBCA.JK: command requires --provider msn"
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: unsupported: BBCA.JK: command requires --provider msn
|
||||
20
tests/golden/help-cache.golden
Normal file
20
tests/golden/help-cache.golden
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Manage local cache
|
||||
|
||||
Usage: idx cache [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
info Show cache usage information
|
||||
clear Clear all cached files
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-o, --output <OUTPUT> Output format [possible values: table, json]
|
||||
--no-color Disable colored output
|
||||
-q, --quiet Suppress non-essential output
|
||||
-v, --verbose... Increase verbosity (-v, -vv)
|
||||
--offline Run without network requests
|
||||
--no-cache Bypass local cache
|
||||
-h, --help Print help
|
||||
--- stderr
|
||||
22
tests/golden/help-config.golden
Normal file
22
tests/golden/help-config.golden
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Manage configuration
|
||||
|
||||
Usage: idx config [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
init Create default configuration file
|
||||
get Get a configuration value by key
|
||||
set Set a configuration value by key
|
||||
path Print configuration file path
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-o, --output <OUTPUT> Output format [possible values: table, json]
|
||||
--no-color Disable colored output
|
||||
-q, --quiet Suppress non-essential output
|
||||
-v, --verbose... Increase verbosity (-v, -vv)
|
||||
--offline Run without network requests
|
||||
--no-cache Bypass local cache
|
||||
-h, --help Print help
|
||||
--- stderr
|
||||
58
tests/golden/help-ownership.golden
Normal file
58
tests/golden/help-ownership.golden
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Ownership intelligence (KSEI + Bing).
|
||||
|
||||
Preferred bootstrap path:
|
||||
1. Run `idx ownership sync` to install or refresh a maintained SQLite snapshot.
|
||||
2. If no snapshot manifest is available, run `idx ownership discover` and then `idx ownership import --url <pdf-url>`.
|
||||
3. Local `.xlsx`, `.pdf`, `.zip`, and `.txt` imports remain available for manual or fallback workflows.
|
||||
|
||||
Usage: idx ownership [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
discover Discover the latest IDX-hosted ownership report URLs
|
||||
import Import ownership data directly from source files
|
||||
sync Install or refresh a maintained ownership SQLite snapshot
|
||||
ticker Show all holders for a ticker (KSEI + Bing combined)
|
||||
entity Show all holdings for an entity across tickers
|
||||
search Search entities by name
|
||||
cross-holders Rank entities by cross-ownership breadth
|
||||
concentration Rank tickers by ownership concentration
|
||||
flow Show Bing institutional flow for a ticker
|
||||
changes Diff two KSEI releases
|
||||
graph Ownership network graph
|
||||
resolve Manual entity resolution workflow
|
||||
releases List imported KSEI releases
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-o, --output <OUTPUT>
|
||||
Output format
|
||||
|
||||
[possible values: table, json]
|
||||
|
||||
--no-color
|
||||
Disable colored output
|
||||
|
||||
-q, --quiet
|
||||
Suppress non-essential output
|
||||
|
||||
-v, --verbose...
|
||||
Increase verbosity (-v, -vv)
|
||||
|
||||
--offline
|
||||
Run without network requests
|
||||
|
||||
--no-cache
|
||||
Bypass local cache
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
|
||||
Examples:
|
||||
idx ownership sync
|
||||
idx ownership sync --manifest /path/to/ownership-snapshot-manifest.json
|
||||
idx ownership discover --limit 1
|
||||
idx ownership import --url <pdf-url>
|
||||
idx ownership releases
|
||||
--- stderr
|
||||
40
tests/golden/help-root.golden
Normal file
40
tests/golden/help-root.golden
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
idx-cli is a Rust command-line tool for Indonesian stock market (IDX) analysis.
|
||||
It supports quote lookup, historical data, local caching, and output formats for both humans and AI agents.
|
||||
|
||||
Usage: idx [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
stocks Stock data and analysis
|
||||
config Manage configuration
|
||||
cache Manage local cache
|
||||
ownership Ownership intelligence (bootstrap with `ownership sync`)
|
||||
completions Generate shell completions
|
||||
version Show idx-cli version
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-o, --output <OUTPUT>
|
||||
Output format
|
||||
|
||||
[possible values: table, json]
|
||||
|
||||
--no-color
|
||||
Disable colored output
|
||||
|
||||
-q, --quiet
|
||||
Suppress non-essential output
|
||||
|
||||
-v, --verbose...
|
||||
Increase verbosity (-v, -vv)
|
||||
|
||||
--offline
|
||||
Run without network requests
|
||||
|
||||
--no-cache
|
||||
Bypass local cache
|
||||
|
||||
-h, --help
|
||||
Print help (see a summary with '-h')
|
||||
--- stderr
|
||||
33
tests/golden/help-stocks.golden
Normal file
33
tests/golden/help-stocks.golden
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Stock data and analysis
|
||||
|
||||
Usage: idx stocks [OPTIONS] <COMMAND>
|
||||
|
||||
Commands:
|
||||
quote Get real-time stock quotes
|
||||
history Get historical OHLC data
|
||||
technical Run technical analysis on a stock
|
||||
growth Run growth analysis on a stock
|
||||
valuation Run valuation analysis on a stock
|
||||
risk Run risk analysis on a stock
|
||||
fundamental Run full fundamental analysis on a stock
|
||||
profile Get company profile
|
||||
financials Get financial statements
|
||||
earnings Get earnings report
|
||||
sentiment Get crowd sentiment
|
||||
insights Get AI insights
|
||||
news Get stock news
|
||||
screen MSN screener
|
||||
compare Compare fundamentals across stocks
|
||||
help Print this message or the help of the given subcommand(s)
|
||||
|
||||
Options:
|
||||
-o, --output <OUTPUT> Output format [possible values: table, json]
|
||||
--no-color Disable colored output
|
||||
-q, --quiet Suppress non-essential output
|
||||
-v, --verbose... Increase verbosity (-v, -vv)
|
||||
--offline Run without network requests
|
||||
--no-cache Bypass local cache
|
||||
-h, --help Print help
|
||||
--- stderr
|
||||
109
tests/golden/ownership-changes.json.golden
Normal file
109
tests/golden/ownership-changes.json.golden
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL ID",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 2400,
|
||||
"new_bps": 2334,
|
||||
"delta_bps": -66
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL IS",
|
||||
"change_type": "increased",
|
||||
"old_bps": 75,
|
||||
"new_bps": 129,
|
||||
"delta_bps": 54
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL MF",
|
||||
"change_type": "increased",
|
||||
"old_bps": 117,
|
||||
"new_bps": 152,
|
||||
"delta_bps": 35
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN MF",
|
||||
"change_type": "increased",
|
||||
"old_bps": 307,
|
||||
"new_bps": 337,
|
||||
"delta_bps": 30
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN OT",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 91,
|
||||
"new_bps": 64,
|
||||
"delta_bps": -27
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN PF",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 76,
|
||||
"new_bps": 56,
|
||||
"delta_bps": -20
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN CP",
|
||||
"change_type": "increased",
|
||||
"old_bps": 72,
|
||||
"new_bps": 86,
|
||||
"delta_bps": 14
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL CP",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 6481,
|
||||
"new_bps": 6467,
|
||||
"delta_bps": -14
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL SC",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 36,
|
||||
"new_bps": 29,
|
||||
"delta_bps": -7
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN IB",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 267,
|
||||
"new_bps": 263,
|
||||
"delta_bps": -4
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN SC",
|
||||
"change_type": "increased",
|
||||
"old_bps": 51,
|
||||
"new_bps": 55,
|
||||
"delta_bps": 4
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE LOCAL PF",
|
||||
"change_type": "increased",
|
||||
"old_bps": 17,
|
||||
"new_bps": 20,
|
||||
"delta_bps": 3
|
||||
},
|
||||
{
|
||||
"ticker_code": "AADI",
|
||||
"entity_name": "KSEI AGGREGATE FOREIGN ID",
|
||||
"change_type": "decreased",
|
||||
"old_bps": 3,
|
||||
"new_bps": 2,
|
||||
"delta_bps": -1
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
32
tests/golden/ownership-changes.table.golden
Normal file
32
tests/golden/ownership-changes.table.golden
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
┌────────┬───────────────────────────┬─────────────────────┬────────┬────────┬────────┐
|
||||
│ TICKER ┆ ENTITY ┆ CHANGE ┆ OLD% ┆ NEW% ┆ DELTA% │
|
||||
╞════════╪═══════════════════════════╪═════════════════════╪════════╪════════╪════════╡
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL ID ┆ [31mDECREASED[39m ┆ 24.00% ┆ 23.34% ┆ -0.66% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL IS ┆ [32mINCREASED[39m ┆ 0.75% ┆ 1.29% ┆ +0.54% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL MF ┆ [32mINCREASED[39m ┆ 1.17% ┆ 1.52% ┆ +0.35% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN MF ┆ [32mINCREASED[39m ┆ 3.07% ┆ 3.37% ┆ +0.30% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN OT ┆ [31mDECREASED[39m ┆ 0.91% ┆ 0.64% ┆ -0.27% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN PF ┆ [31mDECREASED[39m ┆ 0.76% ┆ 0.56% ┆ -0.20% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN CP ┆ [32mINCREASED[39m ┆ 0.72% ┆ 0.86% ┆ +0.14% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL CP ┆ [31mDECREASED[39m ┆ 64.81% ┆ 64.67% ┆ -0.14% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL SC ┆ [31mDECREASED[39m ┆ 0.36% ┆ 0.29% ┆ -0.07% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN IB ┆ [31mDECREASED[39m ┆ 2.67% ┆ 2.63% ┆ -0.04% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN SC ┆ [32mINCREASED[39m ┆ 0.51% ┆ 0.55% ┆ +0.04% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE LOCAL PF ┆ [32mINCREASED[39m ┆ 0.17% ┆ 0.20% ┆ +0.03% │
|
||||
├╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ AADI ┆ KSEI AGGREGATE FOREIGN ID ┆ [31mDECREASED[39m ┆ 0.03% ┆ 0.02% ┆ -0.01% │
|
||||
└────────┴───────────────────────────┴─────────────────────┴────────┴────────┴────────┘
|
||||
--- stderr
|
||||
45
tests/golden/ownership-concentration.json.golden
Normal file
45
tests/golden/ownership-concentration.json.golden
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
[
|
||||
"AALI",
|
||||
{
|
||||
"top1_bps": 7968,
|
||||
"top3_bps": 7968,
|
||||
"hhi": 6348,
|
||||
"free_float_bps": 2032,
|
||||
"holder_count": 1
|
||||
}
|
||||
],
|
||||
[
|
||||
"BBCA",
|
||||
{
|
||||
"top1_bps": 5494,
|
||||
"top3_bps": 5716,
|
||||
"hhi": 3021,
|
||||
"free_float_bps": 4182,
|
||||
"holder_count": 4
|
||||
}
|
||||
],
|
||||
[
|
||||
"AADI",
|
||||
{
|
||||
"top1_bps": 4110,
|
||||
"top3_bps": 6230,
|
||||
"hhi": 2009,
|
||||
"free_float_bps": 2030,
|
||||
"holder_count": 10
|
||||
}
|
||||
],
|
||||
[
|
||||
"ABBA",
|
||||
{
|
||||
"top1_bps": 283,
|
||||
"top3_bps": 418,
|
||||
"hhi": 9,
|
||||
"free_float_bps": 9582,
|
||||
"holder_count": 2
|
||||
}
|
||||
]
|
||||
]
|
||||
--- stderr
|
||||
14
tests/golden/ownership-concentration.table.golden
Normal file
14
tests/golden/ownership-concentration.table.golden
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
┌──────┬────────┬────────┬────────┬──────┬─────────────┬─────────┐
|
||||
│ RANK ┆ TICKER ┆ TOP1% ┆ TOP3% ┆ HHI ┆ FREE_FLOAT% ┆ HOLDERS │
|
||||
╞══════╪════════╪════════╪════════╪══════╪═════════════╪═════════╡
|
||||
│ 1 ┆ AALI ┆ 79.68% ┆ 79.68% ┆ 6348 ┆ 20.32% ┆ 1 │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2 ┆ BBCA ┆ 54.94% ┆ 57.16% ┆ 3021 ┆ 41.82% ┆ 4 │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
|
||||
│ 3 ┆ AADI ┆ 41.10% ┆ 62.30% ┆ 2009 ┆ 20.30% ┆ 10 │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤
|
||||
│ 4 ┆ ABBA ┆ 2.83% ┆ 4.18% ┆ 9 ┆ 95.82% ┆ 2 │
|
||||
└──────┴────────┴────────┴────────┴──────┴─────────────┴─────────┘
|
||||
--- stderr
|
||||
175
tests/golden/ownership-cross-holders.json.golden
Normal file
175
tests/golden/ownership-cross-holders.json.golden
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"entity": {
|
||||
"id": 11,
|
||||
"canonical_name": "ASTRA INTERNATIONAL",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 7968
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 14,
|
||||
"canonical_name": "DWIMURIA INVESTAMA ANDALAN",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 5494
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 4,
|
||||
"canonical_name": "ADARO STRATEGIC INVESTMENTS",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 4110
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 5,
|
||||
"canonical_name": "ALAMTRI RESOURCES INDONESIA TBK PT",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 1537
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 7,
|
||||
"canonical_name": "GARIBALDI THOHIR",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 583
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 2,
|
||||
"canonical_name": "SARATOGA INVESTAMA SEDAYA",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 438
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 9,
|
||||
"canonical_name": "EDWIN SOERYADJAYA",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 352
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 8,
|
||||
"canonical_name": "IR. T. PERMADI RACHMAT",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 348
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 13,
|
||||
"canonical_name": "YAYASAN ABDI BANGSA",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 283
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 10,
|
||||
"canonical_name": "SANDIAGA SALAHUDDIN UNO",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 204
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 1,
|
||||
"canonical_name": "PERSADA CAPITAL INVESTAMA",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 154
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 12,
|
||||
"canonical_name": "YAYASAN BINA SEJAHTERA WARGA BULOG",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 135
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 6,
|
||||
"canonical_name": "FTAG INVESTMENT BANK",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 129
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 17,
|
||||
"canonical_name": "ANTHONI SALIM",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 115
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 3,
|
||||
"canonical_name": "TRINUGRAHA THOHIR",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 115
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 16,
|
||||
"canonical_name": "TRICIPTA MANDHALA GUMILANG",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 107
|
||||
},
|
||||
{
|
||||
"entity": {
|
||||
"id": 15,
|
||||
"canonical_name": "CATURGUWIRATNA SUMAPALA",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"total_bps": 102
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
40
tests/golden/ownership-cross-holders.table.golden
Normal file
40
tests/golden/ownership-cross-holders.table.golden
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
┌──────┬────────────────────────────────────┬─────────┬───────────┐
|
||||
│ RANK ┆ ENTITY ┆ TICKERS ┆ TOTAL_BPS │
|
||||
╞══════╪════════════════════════════════════╪═════════╪═══════════╡
|
||||
│ 1 ┆ ASTRA INTERNATIONAL ┆ 1 ┆ 79.68% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2 ┆ DWIMURIA INVESTAMA ANDALAN ┆ 1 ┆ 54.94% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 3 ┆ ADARO STRATEGIC INVESTMENTS ┆ 1 ┆ 41.10% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 4 ┆ ALAMTRI RESOURCES INDONESIA TBK PT ┆ 1 ┆ 15.37% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 5 ┆ GARIBALDI THOHIR ┆ 1 ┆ 5.83% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 6 ┆ SARATOGA INVESTAMA SEDAYA ┆ 1 ┆ 4.38% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 7 ┆ EDWIN SOERYADJAYA ┆ 1 ┆ 3.52% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 8 ┆ IR. T. PERMADI RACHMAT ┆ 1 ┆ 3.48% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 9 ┆ YAYASAN ABDI BANGSA ┆ 1 ┆ 2.83% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 10 ┆ SANDIAGA SALAHUDDIN UNO ┆ 1 ┆ 2.04% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 11 ┆ PERSADA CAPITAL INVESTAMA ┆ 1 ┆ 1.54% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 12 ┆ YAYASAN BINA SEJAHTERA WARGA BULOG ┆ 1 ┆ 1.35% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 13 ┆ FTAG INVESTMENT BANK ┆ 1 ┆ 1.29% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 14 ┆ ANTHONI SALIM ┆ 1 ┆ 1.15% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 15 ┆ TRINUGRAHA THOHIR ┆ 1 ┆ 1.15% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 16 ┆ TRICIPTA MANDHALA GUMILANG ┆ 1 ┆ 1.07% │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 17 ┆ CATURGUWIRATNA SUMAPALA ┆ 1 ┆ 1.02% │
|
||||
└──────┴────────────────────────────────────┴─────────┴───────────┘
|
||||
--- stderr
|
||||
26
tests/golden/ownership-entity.json.golden
Normal file
26
tests/golden/ownership-entity.json.golden
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"entity": {
|
||||
"id": 17,
|
||||
"canonical_name": "ANTHONI SALIM",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
},
|
||||
"ticker_count": 1,
|
||||
"holdings": [
|
||||
{
|
||||
"ticker": {
|
||||
"id": 4,
|
||||
"code": "BBCA",
|
||||
"name": "BANK CENTRAL ASIA Tbk",
|
||||
"sector": null
|
||||
},
|
||||
"source": "ksei",
|
||||
"shares": 1416306835,
|
||||
"percentage_bps": 115,
|
||||
"report_date": "2026-08-31"
|
||||
}
|
||||
]
|
||||
}
|
||||
--- stderr
|
||||
10
tests/golden/ownership-entity.table.golden
Normal file
10
tests/golden/ownership-entity.table.golden
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mEntity:[0m [1mANTHONI SALIM[0m
|
||||
┌────────┬────────┬───────────────┬───────┬─────────────┐
|
||||
│ TICKER ┆ SOURCE ┆ SHARES ┆ % ┆ REPORT_DATE │
|
||||
╞════════╪════════╪═══════════════╪═══════╪═════════════╡
|
||||
│ BBCA ┆ KSEI ┆ 1,416,306,835 ┆ 1.15% ┆ 2026-08-31 │
|
||||
└────────┴────────┴───────────────┴───────┴─────────────┘
|
||||
[1mTotal tickers:[0m 1
|
||||
--- stderr
|
||||
4
tests/golden/ownership-flow.json.golden
Normal file
4
tests/golden/ownership-flow.json.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
null
|
||||
--- stderr
|
||||
4
tests/golden/ownership-flow.table.golden
Normal file
4
tests/golden/ownership-flow.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
No institutional flow data available for BBCA.
|
||||
--- stderr
|
||||
58
tests/golden/ownership-graph-dot.json.golden
Normal file
58
tests/golden/ownership-graph-dot.json.golden
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "entity:14",
|
||||
"label": "DWIMURIA INVESTAMA ANDALAN",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:15",
|
||||
"label": "CATURGUWIRATNA SUMAPALA",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:16",
|
||||
"label": "TRICIPTA MANDHALA GUMILANG",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:17",
|
||||
"label": "ANTHONI SALIM",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "ticker:BBCA",
|
||||
"label": "BBCA",
|
||||
"node_type": "ticker"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"from": "entity:17",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 115,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:15",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 102,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:14",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 5494,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:16",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 107,
|
||||
"source": "ksei"
|
||||
}
|
||||
]
|
||||
}
|
||||
--- stderr
|
||||
20
tests/golden/ownership-graph-dot.table.golden
Normal file
20
tests/golden/ownership-graph-dot.table.golden
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
digraph ownership {
|
||||
rankdir=LR;
|
||||
graph [fontname="Helvetica"];
|
||||
node [fontname="Helvetica"];
|
||||
edge [fontname="Helvetica"];
|
||||
|
||||
"entity:14" [label="DWIMURIA INVESTAMA ANDALAN", shape=ellipse];
|
||||
"entity:15" [label="CATURGUWIRATNA SUMAPALA", shape=ellipse];
|
||||
"entity:16" [label="TRICIPTA MANDHALA GUMILANG", shape=ellipse];
|
||||
"entity:17" [label="ANTHONI SALIM", shape=ellipse];
|
||||
"ticker:BBCA" [label="BBCA", shape=box];
|
||||
|
||||
"entity:17" -> "ticker:BBCA" [label="1.15% (ksei)"];
|
||||
"entity:15" -> "ticker:BBCA" [label="1.02% (ksei)"];
|
||||
"entity:14" -> "ticker:BBCA" [label="54.94% (ksei)"];
|
||||
"entity:16" -> "ticker:BBCA" [label="1.07% (ksei)"];
|
||||
}
|
||||
--- stderr
|
||||
58
tests/golden/ownership-graph.json.golden
Normal file
58
tests/golden/ownership-graph.json.golden
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "entity:14",
|
||||
"label": "DWIMURIA INVESTAMA ANDALAN",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:15",
|
||||
"label": "CATURGUWIRATNA SUMAPALA",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:16",
|
||||
"label": "TRICIPTA MANDHALA GUMILANG",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "entity:17",
|
||||
"label": "ANTHONI SALIM",
|
||||
"node_type": "entity"
|
||||
},
|
||||
{
|
||||
"id": "ticker:BBCA",
|
||||
"label": "BBCA",
|
||||
"node_type": "ticker"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"from": "entity:17",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 115,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:15",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 102,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:14",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 5494,
|
||||
"source": "ksei"
|
||||
},
|
||||
{
|
||||
"from": "entity:16",
|
||||
"to": "ticker:BBCA",
|
||||
"percentage_bps": 107,
|
||||
"source": "ksei"
|
||||
}
|
||||
]
|
||||
}
|
||||
--- stderr
|
||||
10
tests/golden/ownership-graph.table.golden
Normal file
10
tests/golden/ownership-graph.table.golden
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
nodes: 5 edges: 4
|
||||
|
||||
BBCA [TICKER]
|
||||
├─ DWIMURIA INVESTAMA ANDALAN (54.94%, ksei)
|
||||
├─ ANTHONI SALIM (1.15%, ksei)
|
||||
├─ TRICIPTA MANDHALA GUMILANG (1.07%, ksei)
|
||||
└─ CATURGUWIRATNA SUMAPALA (1.02%, ksei)
|
||||
--- stderr
|
||||
11
tests/golden/ownership-import-archive-txt.json.golden
Normal file
11
tests/golden/ownership-import-archive-txt.json.golden
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"action": "imported",
|
||||
"inserted_rows": 18,
|
||||
"ticker_count": 1,
|
||||
"as_of_date": "2026-02-27",
|
||||
"sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547",
|
||||
"source_url": null
|
||||
}
|
||||
--- stderr
|
||||
4
tests/golden/ownership-import-archive-txt.table.golden
Normal file
4
tests/golden/ownership-import-archive-txt.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Imported 18 rows for 1 tickers (as of 2026-02-27).
|
||||
--- stderr
|
||||
11
tests/golden/ownership-import-xlsx.json.golden
Normal file
11
tests/golden/ownership-import-xlsx.json.golden
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"action": "imported",
|
||||
"inserted_rows": 17,
|
||||
"ticker_count": 4,
|
||||
"as_of_date": "2026-08-31",
|
||||
"sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3",
|
||||
"source_url": null
|
||||
}
|
||||
--- stderr
|
||||
4
tests/golden/ownership-import-xlsx.table.golden
Normal file
4
tests/golden/ownership-import-xlsx.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Imported 17 rows for 4 tickers (as of 2026-08-31).
|
||||
--- stderr
|
||||
4
tests/golden/ownership-releases-empty.json.golden
Normal file
4
tests/golden/ownership-releases-empty.json.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[]
|
||||
--- stderr
|
||||
4
tests/golden/ownership-releases-empty.table.golden
Normal file
4
tests/golden/ownership-releases-empty.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
No ownership releases imported yet.
|
||||
--- stderr
|
||||
29
tests/golden/ownership-releases.json.golden
Normal file
29
tests/golden/ownership-releases.json.golden
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"source_url": null,
|
||||
"sha256": "7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3",
|
||||
"as_of_date": "2026-08-31",
|
||||
"row_count": 17,
|
||||
"imported_at": 0
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"source_url": null,
|
||||
"sha256": "7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547",
|
||||
"as_of_date": "2026-02-27",
|
||||
"row_count": 18,
|
||||
"imported_at": 0
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"source_url": null,
|
||||
"sha256": "efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d",
|
||||
"as_of_date": "2026-01-30",
|
||||
"row_count": 18,
|
||||
"imported_at": 0
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
12
tests/golden/ownership-releases.table.golden
Normal file
12
tests/golden/ownership-releases.table.golden
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
┌────────────┬──────┬──────────────────────────────────────────────────────────────────┬────────┬─────────────┐
|
||||
│ AS_OF ┆ ROWS ┆ SHA256 ┆ SOURCE ┆ IMPORTED_AT │
|
||||
╞════════════╪══════╪══════════════════════════════════════════════════════════════════╪════════╪═════════════╡
|
||||
│ 2026-08-31 ┆ 17 ┆ 7c1a4c209b8ce5bde9112436a0c64d66b042915995b584d2bbf7ca7b15ce81e3 ┆ - ┆ 0 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2026-02-27 ┆ 18 ┆ 7b8da1d820f67062410c48988bb6c70fec2088c24961d481d1c0fce8b9372547 ┆ - ┆ 0 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2026-01-30 ┆ 18 ┆ efb3d8fb19c9e0d803dc1b1734c8389356605d0f78d73db6729fb76d3d75753d ┆ - ┆ 0 │
|
||||
└────────────┴──────┴──────────────────────────────────────────────────────────────────┴────────┴─────────────┘
|
||||
--- stderr
|
||||
8
tests/golden/ownership-resolve-map.json.golden
Normal file
8
tests/golden/ownership-resolve-map.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"action": "mapped",
|
||||
"alias": "SALIM ANTHONI",
|
||||
"entity": "ANTHONI SALIM"
|
||||
}
|
||||
--- stderr
|
||||
4
tests/golden/ownership-resolve-map.table.golden
Normal file
4
tests/golden/ownership-resolve-map.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Mapped alias 'SALIM ANTHONI' -> 'ANTHONI SALIM'.
|
||||
--- stderr
|
||||
11
tests/golden/ownership-resolve-merge.json.golden
Normal file
11
tests/golden/ownership-resolve-merge.json.golden
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"action": "merged",
|
||||
"alias_updates": 1,
|
||||
"bing_holdings_updates": 0,
|
||||
"keep": 1,
|
||||
"ksei_holdings_updates": 1,
|
||||
"merge": 2
|
||||
}
|
||||
--- stderr
|
||||
4
tests/golden/ownership-resolve-merge.table.golden
Normal file
4
tests/golden/ownership-resolve-merge.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Merged entity 2 into 1 (aliases: 1, ksei_holdings: 1, bing_holdings: 0).
|
||||
--- stderr
|
||||
4
tests/golden/ownership-resolve-unresolved.json.golden
Normal file
4
tests/golden/ownership-resolve-unresolved.json.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[]
|
||||
--- stderr
|
||||
4
tests/golden/ownership-resolve-unresolved.table.golden
Normal file
4
tests/golden/ownership-resolve-unresolved.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
No unresolved or low-confidence aliases found.
|
||||
--- stderr
|
||||
11
tests/golden/ownership-search.json.golden
Normal file
11
tests/golden/ownership-search.json.golden
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"id": 17,
|
||||
"canonical_name": "ANTHONI SALIM",
|
||||
"entity_type": null,
|
||||
"country": null
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
8
tests/golden/ownership-search.table.golden
Normal file
8
tests/golden/ownership-search.table.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
┌───────────────┬──────┬─────────┐
|
||||
│ ENTITY ┆ TYPE ┆ COUNTRY │
|
||||
╞═══════════════╪══════╪═════════╡
|
||||
│ ANTHONI SALIM ┆ - ┆ - │
|
||||
└───────────────┴──────┴─────────┘
|
||||
--- stderr
|
||||
8
tests/golden/ownership-ticker-empty.json.golden
Normal file
8
tests/golden/ownership-ticker-empty.json.golden
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
{
|
||||
"code": "SYMBOLNOTFOUND",
|
||||
"error": true,
|
||||
"message": "symbol not found: BBCA"
|
||||
}
|
||||
4
tests/golden/ownership-ticker-empty.table.golden
Normal file
4
tests/golden/ownership-ticker-empty.table.golden
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
exit: 1
|
||||
--- stdout
|
||||
--- stderr
|
||||
Error: symbol not found: BBCA
|
||||
67
tests/golden/ownership-ticker-ksei.json.golden
Normal file
67
tests/golden/ownership-ticker-ksei.json.golden
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"ticker": {
|
||||
"id": 4,
|
||||
"code": "BBCA",
|
||||
"name": "BANK CENTRAL ASIA Tbk",
|
||||
"sector": null
|
||||
},
|
||||
"ksei_as_of": "2026-08-31",
|
||||
"bing_as_of": null,
|
||||
"holders": [
|
||||
{
|
||||
"rank": 1,
|
||||
"source": "ksei",
|
||||
"name": "DWIMURIA INVESTAMA ANDALAN",
|
||||
"entity_id": 14,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 67729950000,
|
||||
"percentage_bps": 5494,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"source": "ksei",
|
||||
"name": "ANTHONI SALIM",
|
||||
"entity_id": 17,
|
||||
"investor_type": "INDIVIDUAL",
|
||||
"locality": "Local",
|
||||
"shares": 1416306835,
|
||||
"percentage_bps": 115,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"source": "ksei",
|
||||
"name": "TRICIPTA MANDHALA GUMILANG",
|
||||
"entity_id": 16,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 1313250000,
|
||||
"percentage_bps": 107,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 4,
|
||||
"source": "ksei",
|
||||
"name": "CATURGUWIRATNA SUMAPALA",
|
||||
"entity_id": 15,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 1261750000,
|
||||
"percentage_bps": 102,
|
||||
"signal": null
|
||||
}
|
||||
],
|
||||
"concentration": {
|
||||
"top1_bps": 5494,
|
||||
"top3_bps": 5716,
|
||||
"hhi": 3021,
|
||||
"free_float_bps": 4182,
|
||||
"holder_count": 4
|
||||
},
|
||||
"flow": null
|
||||
}
|
||||
--- stderr
|
||||
16
tests/golden/ownership-ticker-ksei.table.golden
Normal file
16
tests/golden/ownership-ticker-ksei.table.golden
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mOwnership:[0m [1mBBCA[0m KSEI=2026-08-31 Bing=-
|
||||
┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐
|
||||
│ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │
|
||||
╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡
|
||||
│ 1 ┆ KSEI ┆ DWIMURIA INVESTAMA ANDALAN ┆ CORPORATE ┆ L ┆ 67,729,950,000 ┆ 54.94% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 2 ┆ KSEI ┆ ANTHONI SALIM ┆ INDIVIDUAL ┆ L ┆ 1,416,306,835 ┆ 1.15% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 3 ┆ KSEI ┆ TRICIPTA MANDHALA GUMILANG ┆ CORPORATE ┆ L ┆ 1,313,250,000 ┆ 1.07% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │
|
||||
└──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘
|
||||
[1mConcentration:[0m top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
|
||||
--- stderr
|
||||
67
tests/golden/ownership-ticker.json.golden
Normal file
67
tests/golden/ownership-ticker.json.golden
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"ticker": {
|
||||
"id": 4,
|
||||
"code": "BBCA",
|
||||
"name": "BANK CENTRAL ASIA Tbk",
|
||||
"sector": null
|
||||
},
|
||||
"ksei_as_of": "2026-08-31",
|
||||
"bing_as_of": null,
|
||||
"holders": [
|
||||
{
|
||||
"rank": 1,
|
||||
"source": "ksei",
|
||||
"name": "DWIMURIA INVESTAMA ANDALAN",
|
||||
"entity_id": 14,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 67729950000,
|
||||
"percentage_bps": 5494,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 2,
|
||||
"source": "ksei",
|
||||
"name": "ANTHONI SALIM",
|
||||
"entity_id": 17,
|
||||
"investor_type": "INDIVIDUAL",
|
||||
"locality": "Local",
|
||||
"shares": 1416306835,
|
||||
"percentage_bps": 115,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 3,
|
||||
"source": "ksei",
|
||||
"name": "TRICIPTA MANDHALA GUMILANG",
|
||||
"entity_id": 16,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 1313250000,
|
||||
"percentage_bps": 107,
|
||||
"signal": null
|
||||
},
|
||||
{
|
||||
"rank": 4,
|
||||
"source": "ksei",
|
||||
"name": "CATURGUWIRATNA SUMAPALA",
|
||||
"entity_id": 15,
|
||||
"investor_type": "CORPORATE",
|
||||
"locality": "Local",
|
||||
"shares": 1261750000,
|
||||
"percentage_bps": 102,
|
||||
"signal": null
|
||||
}
|
||||
],
|
||||
"concentration": {
|
||||
"top1_bps": 5494,
|
||||
"top3_bps": 5716,
|
||||
"hhi": 3021,
|
||||
"free_float_bps": 4182,
|
||||
"holder_count": 4
|
||||
},
|
||||
"flow": null
|
||||
}
|
||||
--- stderr
|
||||
16
tests/golden/ownership-ticker.table.golden
Normal file
16
tests/golden/ownership-ticker.table.golden
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mOwnership:[0m [1mBBCA[0m KSEI=2026-08-31 Bing=-
|
||||
┌──────┬────────┬────────────────────────────┬────────────┬─────┬────────────────┬────────┬────────┐
|
||||
│ RANK ┆ SOURCE ┆ INVESTOR ┆ TYPE ┆ L/F ┆ SHARES ┆ % ┆ SIGNAL │
|
||||
╞══════╪════════╪════════════════════════════╪════════════╪═════╪════════════════╪════════╪════════╡
|
||||
│ 1 ┆ KSEI ┆ DWIMURIA INVESTAMA ANDALAN ┆ CORPORATE ┆ L ┆ 67,729,950,000 ┆ 54.94% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 2 ┆ KSEI ┆ ANTHONI SALIM ┆ INDIVIDUAL ┆ L ┆ 1,416,306,835 ┆ 1.15% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 3 ┆ KSEI ┆ TRICIPTA MANDHALA GUMILANG ┆ CORPORATE ┆ L ┆ 1,313,250,000 ┆ 1.07% ┆ - │
|
||||
├╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 4 ┆ KSEI ┆ CATURGUWIRATNA SUMAPALA ┆ CORPORATE ┆ L ┆ 1,261,750,000 ┆ 1.02% ┆ - │
|
||||
└──────┴────────┴────────────────────────────┴────────────┴─────┴────────────────┴────────┴────────┘
|
||||
[1mConcentration:[0m top1=54.94% top3=57.16% hhi=3021 free_float=41.82% holders=4
|
||||
--- stderr
|
||||
81
tests/golden/stocks-compare.json.golden
Normal file
81
tests/golden/stocks-compare.json.golden
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"symbol": "BBCA.JK",
|
||||
"growth": {
|
||||
"revenue_growth": 0.081,
|
||||
"earnings_growth": 0.121,
|
||||
"revenue_growth_pct": 8.1,
|
||||
"earnings_growth_pct": 12.1,
|
||||
"revenue_signal": "slow",
|
||||
"earnings_signal": "moderate",
|
||||
"overall_signal": "mixed"
|
||||
},
|
||||
"valuation": {
|
||||
"pe_trailing": 25.4,
|
||||
"pe_forward": null,
|
||||
"pb": 4.6,
|
||||
"roe": 0.198,
|
||||
"roe_pct": 19.8,
|
||||
"net_margin": 0.324,
|
||||
"net_margin_pct": 32.4,
|
||||
"ev_ebitda": null,
|
||||
"pe_signal": "premium",
|
||||
"pb_signal": "expensive",
|
||||
"roe_signal": "strong",
|
||||
"margin_signal": "excellent",
|
||||
"ev_ebitda_signal": "no data",
|
||||
"overall_signal": "expensive"
|
||||
},
|
||||
"risk": {
|
||||
"debt_to_equity": 0.42,
|
||||
"current_ratio": 1.18,
|
||||
"roa": 0.031,
|
||||
"roa_pct": 3.1,
|
||||
"de_signal": "conservative",
|
||||
"current_ratio_signal": "tight",
|
||||
"overall_signal": "moderate risk"
|
||||
},
|
||||
"overall_signal": "mixed"
|
||||
},
|
||||
{
|
||||
"symbol": "BBRI.JK",
|
||||
"growth": {
|
||||
"revenue_growth": 0.081,
|
||||
"earnings_growth": 0.121,
|
||||
"revenue_growth_pct": 8.1,
|
||||
"earnings_growth_pct": 12.1,
|
||||
"revenue_signal": "slow",
|
||||
"earnings_signal": "moderate",
|
||||
"overall_signal": "mixed"
|
||||
},
|
||||
"valuation": {
|
||||
"pe_trailing": 25.4,
|
||||
"pe_forward": null,
|
||||
"pb": 4.6,
|
||||
"roe": 0.198,
|
||||
"roe_pct": 19.8,
|
||||
"net_margin": 0.324,
|
||||
"net_margin_pct": 32.4,
|
||||
"ev_ebitda": null,
|
||||
"pe_signal": "premium",
|
||||
"pb_signal": "expensive",
|
||||
"roe_signal": "strong",
|
||||
"margin_signal": "excellent",
|
||||
"ev_ebitda_signal": "no data",
|
||||
"overall_signal": "expensive"
|
||||
},
|
||||
"risk": {
|
||||
"debt_to_equity": 0.42,
|
||||
"current_ratio": 1.18,
|
||||
"roa": 0.031,
|
||||
"roa_pct": 3.1,
|
||||
"de_signal": "conservative",
|
||||
"current_ratio_signal": "tight",
|
||||
"overall_signal": "moderate risk"
|
||||
},
|
||||
"overall_signal": "mixed"
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
23
tests/golden/stocks-compare.table.golden
Normal file
23
tests/golden/stocks-compare.table.golden
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mFundamental Comparison[0m
|
||||
┌────────────────┬─────────────────────────┬─────────────────────────┐
|
||||
│ METRIC ┆ BBCA.JK ┆ BBRI.JK │
|
||||
╞════════════════╪═════════════════════════╪═════════════════════════╡
|
||||
│ Symbol ┆ BBCA.JK ┆ BBRI.JK │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Overall ┆ [33mmixed[39m ┆ [33mmixed[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Growth ┆ [33mmixed[39m ┆ [33mmixed[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Valuation ┆ [31mexpensive[39m ┆ [31mexpensive[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Risk ┆ [33mmoderate risk[39m ┆ [33mmoderate risk[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ P/E ┆ 25.40 ┆ 25.40 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ ROE ┆ +19.80% ┆ +19.80% │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Revenue Growth ┆ +8.10% ┆ +8.10% │
|
||||
└────────────────┴─────────────────────────┴─────────────────────────┘
|
||||
--- stderr
|
||||
22
tests/golden/stocks-earnings-forecast-annual.json.golden
Normal file
22
tests/golden/stocks-earnings-forecast-annual.json.golden
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"symbol": "BBCA.JK",
|
||||
"eps_last_year": 1200.0,
|
||||
"revenue_last_year": 100000000000.0,
|
||||
"forecast": [
|
||||
{
|
||||
"eps_actual": null,
|
||||
"eps_forecast": 1300.0,
|
||||
"eps_surprise": null,
|
||||
"eps_surprise_pct": null,
|
||||
"revenue_actual": null,
|
||||
"revenue_forecast": 110000000000.0,
|
||||
"revenue_surprise": null,
|
||||
"earning_release_date": "2026-03-15",
|
||||
"period_type": "2026"
|
||||
}
|
||||
],
|
||||
"history": []
|
||||
}
|
||||
--- stderr
|
||||
10
tests/golden/stocks-earnings-forecast-annual.table.golden
Normal file
10
tests/golden/stocks-earnings-forecast-annual.table.golden
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
|
||||
── Earnings Forecast ──
|
||||
┌────────┬─────────┬─────────────────┬────────────┐
|
||||
│ PERIOD ┆ EPS FC ┆ REV FC ┆ DATE │
|
||||
╞════════╪═════════╪═════════════════╪════════════╡
|
||||
│ FY2026 ┆ 1300.00 ┆ 110,000,000,000 ┆ 2026-03-15 │
|
||||
└────────┴─────────┴─────────────────┴────────────┘
|
||||
--- stderr
|
||||
22
tests/golden/stocks-earnings-history-quarterly.json.golden
Normal file
22
tests/golden/stocks-earnings-history-quarterly.json.golden
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"symbol": "BBCA.JK",
|
||||
"eps_last_year": 1200.0,
|
||||
"revenue_last_year": 100000000000.0,
|
||||
"forecast": [],
|
||||
"history": [
|
||||
{
|
||||
"eps_actual": 320.0,
|
||||
"eps_forecast": null,
|
||||
"eps_surprise": 5.0,
|
||||
"eps_surprise_pct": 1.5,
|
||||
"revenue_actual": 27000000000.0,
|
||||
"revenue_forecast": null,
|
||||
"revenue_surprise": 200000000.0,
|
||||
"earning_release_date": "2025-11-20",
|
||||
"period_type": "Q42025"
|
||||
}
|
||||
]
|
||||
}
|
||||
--- stderr
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
── Earnings History ──
|
||||
┌─────────┬─────────┬────────┬──────────┬───────────┬────────────────┬────────────┐
|
||||
│ PERIOD ┆ EPS ACT ┆ EPS FC ┆ SURPRISE ┆ SURPRISE% ┆ REV ACT ┆ DATE │
|
||||
╞═════════╪═════════╪════════╪══════════╪═══════════╪════════════════╪════════════╡
|
||||
│ Q4 2025 ┆ 320.00 ┆ - ┆ 5.00 ┆ 1.50 ┆ 27,000,000,000 ┆ 2025-11-20 │
|
||||
└─────────┴─────────┴────────┴──────────┴───────────┴────────────────┴────────────┘
|
||||
--- stderr
|
||||
56
tests/golden/stocks-earnings.json.golden
Normal file
56
tests/golden/stocks-earnings.json.golden
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"symbol": "BBCA.JK",
|
||||
"eps_last_year": 1200.0,
|
||||
"revenue_last_year": 100000000000.0,
|
||||
"forecast": [
|
||||
{
|
||||
"eps_actual": null,
|
||||
"eps_forecast": 1300.0,
|
||||
"eps_surprise": null,
|
||||
"eps_surprise_pct": null,
|
||||
"revenue_actual": null,
|
||||
"revenue_forecast": 110000000000.0,
|
||||
"revenue_surprise": null,
|
||||
"earning_release_date": "2026-03-15",
|
||||
"period_type": "2026"
|
||||
},
|
||||
{
|
||||
"eps_actual": null,
|
||||
"eps_forecast": 330.0,
|
||||
"eps_surprise": null,
|
||||
"eps_surprise_pct": null,
|
||||
"revenue_actual": null,
|
||||
"revenue_forecast": 28000000000.0,
|
||||
"revenue_surprise": null,
|
||||
"earning_release_date": "2026-04-20",
|
||||
"period_type": "Q12026"
|
||||
}
|
||||
],
|
||||
"history": [
|
||||
{
|
||||
"eps_actual": 1250.0,
|
||||
"eps_forecast": null,
|
||||
"eps_surprise": 20.0,
|
||||
"eps_surprise_pct": 1.6,
|
||||
"revenue_actual": 105000000000.0,
|
||||
"revenue_forecast": null,
|
||||
"revenue_surprise": 500000000.0,
|
||||
"earning_release_date": "2025-03-15",
|
||||
"period_type": "2025"
|
||||
},
|
||||
{
|
||||
"eps_actual": 320.0,
|
||||
"eps_forecast": null,
|
||||
"eps_surprise": 5.0,
|
||||
"eps_surprise_pct": 1.5,
|
||||
"revenue_actual": 27000000000.0,
|
||||
"revenue_forecast": null,
|
||||
"revenue_surprise": 200000000.0,
|
||||
"earning_release_date": "2025-11-20",
|
||||
"period_type": "Q42025"
|
||||
}
|
||||
]
|
||||
}
|
||||
--- stderr
|
||||
20
tests/golden/stocks-earnings.table.golden
Normal file
20
tests/golden/stocks-earnings.table.golden
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
── Earnings History ──
|
||||
┌─────────┬─────────┬────────┬──────────┬───────────┬─────────────────┬────────────┐
|
||||
│ PERIOD ┆ EPS ACT ┆ EPS FC ┆ SURPRISE ┆ SURPRISE% ┆ REV ACT ┆ DATE │
|
||||
╞═════════╪═════════╪════════╪══════════╪═══════════╪═════════════════╪════════════╡
|
||||
│ FY2025 ┆ 1250.00 ┆ - ┆ 20.00 ┆ 1.60 ┆ 105,000,000,000 ┆ 2025-03-15 │
|
||||
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Q4 2025 ┆ 320.00 ┆ - ┆ 5.00 ┆ 1.50 ┆ 27,000,000,000 ┆ 2025-11-20 │
|
||||
└─────────┴─────────┴────────┴──────────┴───────────┴─────────────────┴────────────┘
|
||||
|
||||
── Earnings Forecast ──
|
||||
┌─────────┬─────────┬─────────────────┬────────────┐
|
||||
│ PERIOD ┆ EPS FC ┆ REV FC ┆ DATE │
|
||||
╞═════════╪═════════╪═════════════════╪════════════╡
|
||||
│ FY2026 ┆ 1300.00 ┆ 110,000,000,000 ┆ 2026-03-15 │
|
||||
├╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Q1 2026 ┆ 330.00 ┆ 28,000,000,000 ┆ 2026-04-20 │
|
||||
└─────────┴─────────┴─────────────────┴────────────┘
|
||||
--- stderr
|
||||
21
tests/golden/stocks-financials-income.json.golden
Normal file
21
tests/golden/stocks-financials-income.json.golden
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"instrument": {
|
||||
"id": "bn91jc",
|
||||
"symbol": "BBCA.JK",
|
||||
"name": "Bank Central Asia Tbk PT"
|
||||
},
|
||||
"balance_sheet": null,
|
||||
"cash_flow": null,
|
||||
"income_statement": {
|
||||
"values": {
|
||||
"netIncome": 400000000.0,
|
||||
"totalRevenue": 1000000000.0
|
||||
},
|
||||
"currency": "IDR",
|
||||
"report_date": "2025-12-31",
|
||||
"end_date": "2025-12-31"
|
||||
}
|
||||
}
|
||||
--- stderr
|
||||
12
tests/golden/stocks-financials-income.table.golden
Normal file
12
tests/golden/stocks-financials-income.table.golden
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
|
||||
── Income Statement (2025-12-31) ──
|
||||
┌───────────────┬───────────────┐
|
||||
│ LINE ITEM ┆ VALUE (IDR) │
|
||||
╞═══════════════╪═══════════════╡
|
||||
│ Net Income ┆ 400,000,000 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Total Revenue ┆ 1,000,000,000 │
|
||||
└───────────────┴───────────────┘
|
||||
--- stderr
|
||||
36
tests/golden/stocks-financials.json.golden
Normal file
36
tests/golden/stocks-financials.json.golden
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"instrument": {
|
||||
"id": "bn91jc",
|
||||
"symbol": "BBCA.JK",
|
||||
"name": "Bank Central Asia Tbk PT"
|
||||
},
|
||||
"balance_sheet": {
|
||||
"values": {
|
||||
"cashAndShortTermInvestments": 500000000.0,
|
||||
"totalEquity": 900000000.0
|
||||
},
|
||||
"currency": "IDR",
|
||||
"report_date": "2025-12-31",
|
||||
"end_date": "2025-12-31"
|
||||
},
|
||||
"cash_flow": {
|
||||
"values": {
|
||||
"operatingCashFlow": 600000000.0
|
||||
},
|
||||
"currency": "IDR",
|
||||
"report_date": "2025-12-31",
|
||||
"end_date": "2025-12-31"
|
||||
},
|
||||
"income_statement": {
|
||||
"values": {
|
||||
"netIncome": 400000000.0,
|
||||
"totalRevenue": 1000000000.0
|
||||
},
|
||||
"currency": "IDR",
|
||||
"report_date": "2025-12-31",
|
||||
"end_date": "2025-12-31"
|
||||
}
|
||||
}
|
||||
--- stderr
|
||||
28
tests/golden/stocks-financials.table.golden
Normal file
28
tests/golden/stocks-financials.table.golden
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
|
||||
── Income Statement (2025-12-31) ──
|
||||
┌───────────────┬───────────────┐
|
||||
│ LINE ITEM ┆ VALUE (IDR) │
|
||||
╞═══════════════╪═══════════════╡
|
||||
│ Net Income ┆ 400,000,000 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Total Revenue ┆ 1,000,000,000 │
|
||||
└───────────────┴───────────────┘
|
||||
|
||||
── Balance Sheet (2025-12-31) ──
|
||||
┌─────────────────────────────────┬─────────────┐
|
||||
│ LINE ITEM ┆ VALUE (IDR) │
|
||||
╞═════════════════════════════════╪═════════════╡
|
||||
│ Cash And Short Term Investments ┆ 500,000,000 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Total Equity ┆ 900,000,000 │
|
||||
└─────────────────────────────────┴─────────────┘
|
||||
|
||||
── Cash Flow (2025-12-31) ──
|
||||
┌─────────────────────┬─────────────┐
|
||||
│ LINE ITEM ┆ VALUE (IDR) │
|
||||
╞═════════════════════╪═════════════╡
|
||||
│ Operating Cash Flow ┆ 600,000,000 │
|
||||
└─────────────────────┴─────────────┘
|
||||
--- stderr
|
||||
41
tests/golden/stocks-fundamental.json.golden
Normal file
41
tests/golden/stocks-fundamental.json.golden
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"symbol": "BBCA.JK",
|
||||
"growth": {
|
||||
"revenue_growth": 0.081,
|
||||
"earnings_growth": 0.121,
|
||||
"revenue_growth_pct": 8.1,
|
||||
"earnings_growth_pct": 12.1,
|
||||
"revenue_signal": "slow",
|
||||
"earnings_signal": "moderate",
|
||||
"overall_signal": "mixed"
|
||||
},
|
||||
"valuation": {
|
||||
"pe_trailing": 25.4,
|
||||
"pe_forward": null,
|
||||
"pb": 4.6,
|
||||
"roe": 0.198,
|
||||
"roe_pct": 19.8,
|
||||
"net_margin": 0.324,
|
||||
"net_margin_pct": 32.4,
|
||||
"ev_ebitda": null,
|
||||
"pe_signal": "premium",
|
||||
"pb_signal": "expensive",
|
||||
"roe_signal": "strong",
|
||||
"margin_signal": "excellent",
|
||||
"ev_ebitda_signal": "no data",
|
||||
"overall_signal": "expensive"
|
||||
},
|
||||
"risk": {
|
||||
"debt_to_equity": 0.42,
|
||||
"current_ratio": 1.18,
|
||||
"roa": 0.031,
|
||||
"roa_pct": 3.1,
|
||||
"de_signal": "conservative",
|
||||
"current_ratio_signal": "tight",
|
||||
"overall_signal": "moderate risk"
|
||||
},
|
||||
"overall_signal": "mixed"
|
||||
}
|
||||
--- stderr
|
||||
49
tests/golden/stocks-fundamental.table.golden
Normal file
49
tests/golden/stocks-fundamental.table.golden
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mFundamental Analysis for BBCA.JK[0m
|
||||
|
||||
[1mGrowth Analysis for BBCA.JK[0m
|
||||
┌─────────────────┬─────────┬────────────────────┐
|
||||
│ METRIC ┆ VALUE ┆ SIGNAL │
|
||||
╞═════════════════╪═════════╪════════════════════╡
|
||||
│ Revenue Growth ┆ +8.10% ┆ [33mslow[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Earnings Growth ┆ +12.10% ┆ [32mmoderate[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Overall ┆ - ┆ [33mmixed[39m │
|
||||
└─────────────────┴─────────┴────────────────────┘
|
||||
|
||||
[1mValuation Analysis for BBCA.JK[0m
|
||||
┌────────────────┬─────────┬─────────────────────┐
|
||||
│ METRIC ┆ VALUE ┆ SIGNAL │
|
||||
╞════════════════╪═════════╪═════════════════════╡
|
||||
│ P/E (Trailing) ┆ 25.40 ┆ [33mpremium[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ P/E (Forward) ┆ - ┆ - │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Price/Book ┆ 4.60 ┆ [31mexpensive[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ ROE ┆ +19.80% ┆ [32mstrong[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Net Margin ┆ +32.40% ┆ [32mexcellent[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ EV/EBITDA ┆ - ┆ [33mno data[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Overall ┆ - ┆ [31mexpensive[39m │
|
||||
└────────────────┴─────────┴─────────────────────┘
|
||||
|
||||
[1mRisk Analysis for BBCA.JK[0m
|
||||
┌───────────────┬────────┬─────────────────────────┐
|
||||
│ METRIC ┆ VALUE ┆ SIGNAL │
|
||||
╞═══════════════╪════════╪═════════════════════════╡
|
||||
│ Debt/Equity ┆ 0.42 ┆ [32mconservative[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Current Ratio ┆ 1.18 ┆ [33mtight[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ ROA ┆ +3.10% ┆ - │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Overall ┆ - ┆ [33mmoderate risk[39m │
|
||||
└───────────────┴────────┴─────────────────────────┘
|
||||
|
||||
[1mOverall Signal:[0m [33mmixed[39m
|
||||
--- stderr
|
||||
12
tests/golden/stocks-growth.json.golden
Normal file
12
tests/golden/stocks-growth.json.golden
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"revenue_growth": 0.081,
|
||||
"earnings_growth": 0.121,
|
||||
"revenue_growth_pct": 8.1,
|
||||
"earnings_growth_pct": 12.1,
|
||||
"revenue_signal": "slow",
|
||||
"earnings_signal": "moderate",
|
||||
"overall_signal": "mixed"
|
||||
}
|
||||
--- stderr
|
||||
13
tests/golden/stocks-growth.table.golden
Normal file
13
tests/golden/stocks-growth.table.golden
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mGrowth Analysis for BBCA.JK[0m
|
||||
┌─────────────────┬─────────┬────────────────────┐
|
||||
│ METRIC ┆ VALUE ┆ SIGNAL │
|
||||
╞═════════════════╪═════════╪════════════════════╡
|
||||
│ Revenue Growth ┆ +8.10% ┆ [33mslow[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Earnings Growth ┆ +12.10% ┆ [32mmoderate[39m │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ Overall ┆ - ┆ [33mmixed[39m │
|
||||
└─────────────────┴─────────┴────────────────────┘
|
||||
--- stderr
|
||||
29
tests/golden/stocks-history-msn.json.golden
Normal file
29
tests/golden/stocks-history-msn.json.golden
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"date": "2026-01-14",
|
||||
"open": 8000,
|
||||
"high": 8000,
|
||||
"low": 8000,
|
||||
"close": 8000,
|
||||
"volume": 0
|
||||
},
|
||||
{
|
||||
"date": "2026-01-15",
|
||||
"open": 8075,
|
||||
"high": 8075,
|
||||
"low": 8075,
|
||||
"close": 8075,
|
||||
"volume": 0
|
||||
},
|
||||
{
|
||||
"date": "2026-01-16",
|
||||
"open": 8025,
|
||||
"high": 8025,
|
||||
"low": 8025,
|
||||
"close": 8025,
|
||||
"volume": 0
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
13
tests/golden/stocks-history-msn.table.golden
Normal file
13
tests/golden/stocks-history-msn.table.golden
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mHistory for BBCA.JK[0m
|
||||
┌────────────┬───────┬───────┬───────┬───────┬────────┐
|
||||
│ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │
|
||||
╞════════════╪═══════╪═══════╪═══════╪═══════╪════════╡
|
||||
│ 2026-01-14 ┆ 8,000 ┆ 8,000 ┆ 8,000 ┆ 8,000 ┆ 0 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 2026-01-15 ┆ 8,075 ┆ 8,075 ┆ 8,075 ┆ 8,075 ┆ 0 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┤
|
||||
│ 2026-01-16 ┆ 8,025 ┆ 8,025 ┆ 8,025 ┆ 8,025 ┆ 0 │
|
||||
└────────────┴───────┴───────┴───────┴───────┴────────┘
|
||||
--- stderr
|
||||
29
tests/golden/stocks-history.json.golden
Normal file
29
tests/golden/stocks-history.json.golden
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"date": "2024-03-01",
|
||||
"open": 9800,
|
||||
"high": 9900,
|
||||
"low": 9750,
|
||||
"close": 9875,
|
||||
"volume": 12300000
|
||||
},
|
||||
{
|
||||
"date": "2024-03-02",
|
||||
"open": 9850,
|
||||
"high": 9920,
|
||||
"low": 9800,
|
||||
"close": 9880,
|
||||
"volume": 11000000
|
||||
},
|
||||
{
|
||||
"date": "2024-03-03",
|
||||
"open": 9860,
|
||||
"high": 9950,
|
||||
"low": 9820,
|
||||
"close": 9925,
|
||||
"volume": 14000000
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
14
tests/golden/stocks-history.table.golden
Normal file
14
tests/golden/stocks-history.table.golden
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[1mHistory for BBCA.JK[0m
|
||||
┌────────────┬───────┬───────┬───────┬───────┬────────────┐
|
||||
│ DATE ┆ OPEN ┆ HIGH ┆ LOW ┆ CLOSE ┆ VOLUME │
|
||||
╞════════════╪═══════╪═══════╪═══════╪═══════╪════════════╡
|
||||
│ 2024-03-01 ┆ 9,800 ┆ 9,900 ┆ 9,750 ┆ 9,875 ┆ 12,300,000 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2024-03-02 ┆ 9,850 ┆ 9,920 ┆ 9,800 ┆ 9,880 ┆ 11,000,000 │
|
||||
├╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌┤
|
||||
│ 2024-03-03 ┆ 9,860 ┆ 9,950 ┆ 9,820 ┆ 9,925 ┆ 14,000,000 │
|
||||
└────────────┴───────┴───────┴───────┴───────┴────────────┘
|
||||
--- stderr
|
||||
info: history provider fallback active (msn -> yahoo)
|
||||
16
tests/golden/stocks-insights.json.golden
Normal file
16
tests/golden/stocks-insights.json.golden
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
{
|
||||
"id": "bn91jc",
|
||||
"symbol": "BBCA.JK",
|
||||
"summary": "Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.",
|
||||
"highlights": [
|
||||
"P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers",
|
||||
"Analyst price target: Analysts forecast more than 20% upside for 12-month price target"
|
||||
],
|
||||
"risks": [
|
||||
"Quarterly Revenue YoY Growth: Revenue grew worse than peers"
|
||||
],
|
||||
"last_updated": "2026-03-26T04:14:57.9197955Z"
|
||||
}
|
||||
--- stderr
|
||||
10
tests/golden/stocks-insights.table.golden
Normal file
10
tests/golden/stocks-insights.table.golden
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
Mixed analyst signals for Bank Central Asia Tbk PT: 1 positive, 1 negative, 1 neutral.
|
||||
Last updated: 2026-03-26T04:14:57.9197955Z
|
||||
Highlights:
|
||||
- P/E ratio: Earnings data unavailable, can't compare P/E ratio with peers
|
||||
- Analyst price target: Analysts forecast more than 20% upside for 12-month price target
|
||||
Risks:
|
||||
- Quarterly Revenue YoY Growth: Revenue grew worse than peers
|
||||
--- stderr
|
||||
15
tests/golden/stocks-news.json.golden
Normal file
15
tests/golden/stocks-news.json.golden
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
exit: 0
|
||||
--- stdout
|
||||
[
|
||||
{
|
||||
"id": "news-1",
|
||||
"symbol": "BBCA.JK",
|
||||
"title": "BCA reports steady growth",
|
||||
"url": "https://example.com/news/bca-growth",
|
||||
"description": "BCA delivered steady quarterly growth across lending and deposits.",
|
||||
"provider": "Contoso News",
|
||||
"published_at": "2026-03-20T10:00:00Z",
|
||||
"read_time_min": 3
|
||||
}
|
||||
]
|
||||
--- stderr
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue