116 lines
4 KiB
Python
116 lines
4 KiB
Python
|
|
import datetime
|
||
|
|
import os
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from suvi import paths
|
||
|
|
|
||
|
|
GOOD = "dr_suvi-l2-ci171_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits"
|
||
|
|
|
||
|
|
|
||
|
|
def test_parses_a_real_filename():
|
||
|
|
frame = paths.parse_frame_filename(GOOD)
|
||
|
|
assert frame.satellite == 16
|
||
|
|
assert frame.wavelength == 171
|
||
|
|
assert frame.version == "1-0-2"
|
||
|
|
assert frame.label is None
|
||
|
|
assert frame.t_end - frame.t_start == paths.CADENCE
|
||
|
|
assert frame.t_start == int(
|
||
|
|
datetime.datetime(2024, 5, 10, tzinfo=datetime.timezone.utc).timestamp()
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("label", [None, "f", "e"])
|
||
|
|
def test_filename_round_trips(label):
|
||
|
|
name = GOOD if label is None else GOOD.replace(".fits", f"_{label}.fits")
|
||
|
|
frame = paths.parse_frame_filename(name)
|
||
|
|
assert frame.label == label
|
||
|
|
assert frame.filename(label) == name
|
||
|
|
|
||
|
|
|
||
|
|
def test_double_suffix_keeps_outermost_label():
|
||
|
|
"""Re-processing an already-labelled file produced '..._f_f.fits' in the archive."""
|
||
|
|
frame = paths.parse_frame_filename(GOOD.replace(".fits", "_f_e.fits"))
|
||
|
|
assert frame.label == "e"
|
||
|
|
assert frame.filename() == GOOD
|
||
|
|
|
||
|
|
|
||
|
|
def test_relpath_files_under_the_start_date():
|
||
|
|
"""A frame opening at 23:58 belongs to that day, not the day it ends on."""
|
||
|
|
name = "dr_suvi-l2-ci304_g18_s20241231T235800Z_e20250101T000200Z_v1-0-2.fits"
|
||
|
|
frame = paths.parse_frame_filename(name)
|
||
|
|
assert frame.relpath() == (
|
||
|
|
"goes18/l2/data/suvi-l2-ci304/2024/12/31/" + name
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_slot_identifies_the_observation():
|
||
|
|
frame = paths.parse_frame_filename(GOOD)
|
||
|
|
labelled = paths.parse_frame_filename(GOOD.replace(".fits", "_e.fits"))
|
||
|
|
assert frame.slot == labelled.slot == (16, 171, frame.t_start)
|
||
|
|
|
||
|
|
|
||
|
|
def test_error_plot_name_matches_legacy_filter_output():
|
||
|
|
frame = paths.parse_frame_filename(GOOD)
|
||
|
|
assert frame.error_plot_name() == GOOD.replace(".fits", "_e.jpg")
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize(
|
||
|
|
"name",
|
||
|
|
[
|
||
|
|
"",
|
||
|
|
"x" * 300,
|
||
|
|
"README.txt",
|
||
|
|
"dr_suvi-l2-ci171_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits.bak",
|
||
|
|
"dr_suvi-l2-ci999_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits", # bad band
|
||
|
|
"dr_suvi-l2-ci171_g17_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits", # excluded sat
|
||
|
|
"dr_suvi-l2-ci171_g16_s20241310T000000Z_e20240510T000400Z_v1-0-2.fits", # month 13
|
||
|
|
"dr_suvi-l2-ci171_g16_s20240510T000400Z_e20240510T000000Z_v1-0-2.fits", # end <= start
|
||
|
|
"dr_suvi-l2-ci171_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2_x.fits", # bad label
|
||
|
|
"../dr_suvi-l2-ci171_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits",
|
||
|
|
"sub/dr_suvi-l2-ci171_g16_s20240510T000000Z_e20240510T000400Z_v1-0-2.fits",
|
||
|
|
],
|
||
|
|
)
|
||
|
|
def test_rejects_malformed_names(name):
|
||
|
|
assert paths.parse_frame_filename(name) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_rejects_non_string_input():
|
||
|
|
for value in (None, 42, b"bytes", ["list"]):
|
||
|
|
assert paths.parse_frame_filename(value) is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_filename_rejects_unknown_label():
|
||
|
|
frame = paths.parse_frame_filename(GOOD)
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
frame.filename("q")
|
||
|
|
|
||
|
|
|
||
|
|
def test_abspath_resolves_within_the_root(tmp_path):
|
||
|
|
frame = paths.parse_frame_filename(GOOD)
|
||
|
|
resolved = paths.abspath(frame.relpath(), root=str(tmp_path))
|
||
|
|
assert resolved.startswith(str(tmp_path) + os.sep)
|
||
|
|
assert resolved.endswith(GOOD)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize("hostile", ["../../etc/passwd", "goes16/../../../etc/passwd"])
|
||
|
|
def test_abspath_refuses_to_escape_the_root(tmp_path, hostile):
|
||
|
|
with pytest.raises(ValueError):
|
||
|
|
paths.abspath(hostile, root=str(tmp_path))
|
||
|
|
|
||
|
|
|
||
|
|
def test_data_root_honours_the_environment(archive):
|
||
|
|
assert paths.data_root() == str(archive)
|
||
|
|
|
||
|
|
|
||
|
|
def test_default_db_path_honours_the_environment(monkeypatch, tmp_path):
|
||
|
|
target = tmp_path / "scratch.sqlite"
|
||
|
|
monkeypatch.setenv("SUVI_DB", str(target))
|
||
|
|
assert paths.default_db_path() == str(target)
|
||
|
|
|
||
|
|
|
||
|
|
def test_wavelength_dirs_covers_every_combination(tmp_path):
|
||
|
|
dirs = paths.wavelength_dirs(str(tmp_path))
|
||
|
|
assert len(dirs) == len(paths.SATELLITES) * len(paths.WAVELENGTHS)
|
||
|
|
assert len(set(dirs)) == len(dirs)
|