Add KSEI archive fallback import

This commit is contained in:
Rasyidan Akbar F. 2026-03-31 19:29:00 +07:00
commit 1f5c4b2798
11 changed files with 622 additions and 59 deletions

View file

@ -10,6 +10,7 @@ use assert_cmd::Command;
use predicates::prelude::*;
use rusqlite::Connection;
use sha2::{Digest, Sha256};
use zip::write::SimpleFileOptions;
fn bin() -> Command {
let current = std::env::current_exe().expect("current test executable path");
@ -167,6 +168,18 @@ fn pdf_url(base: &str, name: &str) -> String {
format!("{base}/{name}.pdf")
}
fn write_zip_with_text(zip_path: &Path, entry_name: &str, text: &str) {
let file = fs::File::create(zip_path).expect("create zip fixture");
let mut writer = zip::ZipWriter::new(file);
writer
.start_file(entry_name, SimpleFileOptions::default())
.expect("start zip file");
writer
.write_all(text.as_bytes())
.expect("write zip fixture text");
writer.finish().expect("finish zip fixture");
}
fn sha256_hex(bytes: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(bytes);
@ -1175,6 +1188,111 @@ fn ownership_import_url_rejects_legacy_investor_type_pdf_schema() {
));
}
#[test]
fn ownership_import_file_txt_archive_succeeds() {
let root = test_env_dir("ownership-import-txt-archive");
let db_path = root.join("ownership.db");
let txt_path = root.join("Balancepos20260227.txt");
fs::write(
&txt_path,
include_str!("fixtures/ksei_balancepos_20260227_excerpt.txt"),
)
.expect("write txt archive fixture");
bin_with_root(&root)
.args([
"config",
"set",
"ownership.db_path",
db_path.to_str().unwrap(),
])
.assert()
.success();
bin_with_root(&root)
.args(["ownership", "import", "--file", txt_path.to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("Imported 18 rows for 1 tickers"));
bin_with_root(&root)
.args(["ownership", "ticker", "AADI", "--source", "ksei"])
.assert()
.success()
.stdout(predicate::str::contains("KSEI AGGREGATE LOCAL CP"))
.stdout(predicate::str::contains("64.67%"));
}
#[test]
fn ownership_import_file_zip_archive_supports_releases_ticker_and_changes() {
let root = test_env_dir("ownership-import-zip-archive");
let db_path = root.join("ownership.db");
let jan_zip = root.join("BalanceposEfek20260130.zip");
let feb_zip = root.join("BalanceposEfek20260227.zip");
write_zip_with_text(
&jan_zip,
"Balancepos20260130.txt",
include_str!("fixtures/ksei_balancepos_20260130_excerpt.txt"),
);
write_zip_with_text(
&feb_zip,
"Balancepos20260227.txt",
include_str!("fixtures/ksei_balancepos_20260227_excerpt.txt"),
);
bin_with_root(&root)
.args([
"config",
"set",
"ownership.db_path",
db_path.to_str().unwrap(),
])
.assert()
.success();
bin_with_root(&root)
.args(["ownership", "import", "--file", jan_zip.to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("Imported 18 rows for 1 tickers"));
bin_with_root(&root)
.args(["ownership", "import", "--file", feb_zip.to_str().unwrap()])
.assert()
.success()
.stdout(predicate::str::contains("Imported 18 rows for 1 tickers"));
bin_with_root(&root)
.args(["ownership", "releases"])
.assert()
.success()
.stdout(predicate::str::contains("2026-02-27"))
.stdout(predicate::str::contains("2026-01-30"));
bin_with_root(&root)
.args(["ownership", "ticker", "AADI", "--source", "ksei"])
.assert()
.success()
.stdout(predicate::str::contains("KSEI AGGREGATE LOCAL CP"))
.stdout(predicate::str::contains("KSEI AGGREGATE FOREIGN MF"));
bin_with_root(&root)
.args([
"ownership",
"changes",
"--from",
"2026-01-30",
"--to",
"2026-02-27",
])
.assert()
.success()
.stdout(predicate::str::contains("AADI"))
.stdout(predicate::str::contains("KSEI AGGREGATE LOCAL CP"))
.stdout(predicate::str::contains("DECREASED"));
}
#[test]
fn ownership_sync_installs_snapshot_and_preserves_query_behavior() {
let publisher_root = test_env_dir("ownership-sync-publisher");

View file

@ -0,0 +1,2 @@
Date|Code|Type|Sec. Num|Price|Local IS|Local CP|Local PF|Local IB|Local ID|Local MF|Local SC|Local FD|Local OT|Total|Foreign IS|Foreign CP|Foreign PF|Foreign IB|Foreign ID|Foreign MF|Foreign SC|Foreign FD|Foreign OT|Total
30-JAN-2026|AADI|EQUITY|7786891760|7600|58704495|5046580437|13135269|41|1868661103|91475881|27984191|1966845|85275|7108593537|1150700|55820749|58897143|207783974|2127405|239139003|39765137|2493100|71121012|678298223

View file

@ -0,0 +1,2 @@
Date|Code|Type|Sec. Num|Price|Local IS|Local CP|Local PF|Local IB|Local ID|Local MF|Local SC|Local FD|Local OT|Total|Foreign IS|Foreign CP|Foreign PF|Foreign IB|Foreign ID|Foreign MF|Foreign SC|Foreign FD|Foreign OT|Total
27-FEB-2026|AADI|EQUITY|7786891760|9250|100730965|5035745466|15622269|41|1817092689|117982099|22932698|2060140|105275|7112271642|1150700|66705786|43311343|204936195|1261705|262226503|42723690|2493100|49811096|674620118