@@ -532,6 +532,61 @@ def test_run_conversion_aggregate_skips_duplicate_part_payloads(self):
532532 self .assertEqual (lines .count ("<dup> <p> <o> ." ), 1 )
533533 self .assertEqual (lines .count ("<uniq> <p> <o> ." ), 1 )
534534
535+ def test_run_conversion_rewrites_dot_literal_to_typed_null (self ):
536+ """Plain literal dot is normalized to ontology null typed literal in RDF output."""
537+ with tempfile .TemporaryDirectory () as td :
538+ tmp_path = Path (td )
539+ fake_bin = tmp_path / "bin"
540+ fake_bin .mkdir ()
541+ make_executable (
542+ fake_bin / "java" ,
543+ """#!/usr/bin/env bash
544+ set -euo pipefail
545+ if [[ "${1:-}" == "-version" ]]; then
546+ echo 'openjdk version "11.0.0"' >&2
547+ exit 0
548+ fi
549+ out=""
550+ while [[ $# -gt 0 ]]; do
551+ if [[ "$1" == "-o" ]]; then
552+ out="$2"
553+ shift 2
554+ continue
555+ fi
556+ shift
557+ done
558+ mkdir -p "$out"
559+ printf '<s> <p> "." .\\ n' > "$out/part-000"
560+ """ ,
561+ )
562+
563+ out_dir = tmp_path / "out"
564+ metrics_dir = tmp_path / "metrics"
565+ rules = tmp_path / "rules.ttl"
566+ rules .write_text ("@prefix ex: <http://example.org/> .\n " )
567+ vcf = tmp_path / "input.vcf"
568+ vcf .write_text ("##fileformat=VCFv4.2\n #CHROM\t POS\n 1\t 5\n " )
569+
570+ env = env_with_path (fake_bin )
571+ env .update (
572+ {
573+ "JAR" : "fake.jar" ,
574+ "IN" : str (rules ),
575+ "IN_VCF" : str (vcf ),
576+ "OUT_DIR" : str (out_dir ),
577+ "OUT_NAME" : "rdf" ,
578+ "LOGDIR" : str (metrics_dir ),
579+ "RUN_ID" : "run-null-dot" ,
580+ "TIMESTAMP" : "2026-01-01T00:00:00" ,
581+ }
582+ )
583+
584+ result = subprocess .run (["bash" , str (SCRIPT )], env = env , capture_output = True , text = True )
585+ self .assertEqual (result .returncode , 0 , msg = result .stderr )
586+ merged_nt = out_dir / "rdf" / "rdf.nt"
587+ text = merged_nt .read_text ()
588+ self .assertIn ('"."^^<https://w3id.org/vcf-rdfizer/vocab#Null> .' , text )
589+
535590
536591if __name__ == "__main__" :
537592 unittest .main ()
0 commit comments