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)