Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 134 additions & 1 deletion slitlessutils/conftest.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import os

import numpy as np
import pytest
from astropy.io import fits
from astropy.wcs import WCS

from slitlessutils import config

cfg = config.Config()

# download the latest reference files
reffile = cfg.retrieve_reffiles(update=True)


Expand All @@ -20,3 +22,134 @@ def _jail(tmp_path):
yield str(tmp_path)
finally:
os.chdir(old_dir)


@pytest.fixture(scope='session')
def test_data_dir(tmp_path_factory):
"""
Create a session-scoped temporary directory for test data.

This is useful for expensive fixtures that can be shared across tests.
"""
return tmp_path_factory.mktemp("slitlessutils_test_data")


@pytest.fixture
def basic_wcs_header():
"""
Create a basic WCS header for testing.

Returns a header with valid WCS for a 100x100 image centered
at RA=53, Dec=-27 with 0.05 arcsec/pixel scale.
"""
w = WCS(naxis=2)
w.wcs.crpix = [50., 50.]
w.wcs.crval = [53.0, -27.0]
w.wcs.ctype = ['RA---TAN', 'DEC--TAN']
w.wcs.cd = [[-0.05 / 3600., 0.], [0., 0.05 / 3600.]]
hdr = w.to_header()

# Standard HST keywords
hdr['TELESCOP'] = 'HST'
hdr['INSTRUME'] = 'WFC3'
hdr['FILTER'] = 'F105W'

return hdr


@pytest.fixture
def simple_segmentation_and_direct(tmp_path, basic_wcs_header):
"""
Create simple segmentation and direct images for testing.

Returns tuple of (segmentation_path, direct_image_path).

The images contain 2 point-like sources:
- Source 1: at pixel (30, 30), bright
- Source 2: at pixel (70, 70), fainter
"""
npix = 100
img = np.zeros((npix, npix), dtype=np.float32)
seg = np.zeros((npix, npix), dtype=np.int32)

# Create source footprints
y, x = np.ogrid[:npix, :npix]

# Source 1
r1 = np.sqrt((x - 30)**2 + (y - 30)**2)
img[r1 < 5] = 100.0
seg[r1 < 3] = 1

# Source 2
r2 = np.sqrt((x - 70)**2 + (y - 70)**2)
img[r2 < 5] = 50.0
seg[r2 < 3] = 2

seg_path = str(tmp_path / 'test_seg.fits')
sci_path = str(tmp_path / 'test_sci.fits')

fits.writeto(seg_path, seg, header=basic_wcs_header, overwrite=True)
fits.writeto(sci_path, img, header=basic_wcs_header, overwrite=True)

return seg_path, sci_path


@pytest.fixture
def sample_wcs_csv_content():
"""
Return sample CSV content for simulated WFSSCollection tests.

This CSV defines 3 simulated observations at different orientations.
"""
return """dataset,ra,dec,orientat,telescope,instrument,disperser,blocking
sim_a,53.162,-27.791,0.0,HST,WFC3IR,G102,
sim_b,53.162,-27.791,60.0,HST,WFC3IR,G102,
sim_c,53.162,-27.791,120.0,HST,WFC3IR,G102,
"""


@pytest.fixture
def sample_wcs_csv(tmp_path, sample_wcs_csv_content):
"""Create a sample WCS CSV file for testing WFSSCollection."""
csv_file = tmp_path / "test_wcs.csv"
csv_file.write_text(sample_wcs_csv_content)
return str(csv_file)


@pytest.fixture
def wfss_detector():
"""Create a simulated WFSS and return the first detector for testing."""
import slitlessutils as su
wfss = su.wfss.WFSS.simulated(
'HST', 'WFC3IR', 'test',
53.0, -27.0, 0.0, 'G102',
exptime=1000., background=0.5
)
for detname, detector in wfss.items():
return detname, detector


@pytest.fixture
def sample_throughput():
"""Create a simple top-hat throughput for testing."""
import slitlessutils as su
wave = np.linspace(10000., 11000., 100)
trans = np.ones_like(wave)
trans[:10] = 0.
trans[-10:] = 0.
return su.photometry.Throughput(wave, trans, unit='angstroms')


@pytest.fixture
def flat_sed():
"""Create a flat SED for testing with proper flux values."""
import slitlessutils as su
wave = np.linspace(8000., 12000., 100)
flux = np.ones_like(wave) * 1e-16
return su.photometry.SED(wave, flux)


@pytest.fixture
def slitlessutils_config():
"""Return the Config singleton."""
return config.Config()
100 changes: 100 additions & 0 deletions slitlessutils/tests/test_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
"""
Tests for the slitlessutils configuration module.

The Config class is a singleton that manages global settings and reference
files. These tests verify that configuration is correctly loaded and
accessible, which is essential for all downstream operations.
"""

import os

import numpy as np

from slitlessutils import config


def test_config_singleton_returns_same_instance():
"""Verify that Config() always returns the same instance."""
cfg1 = config.Config()
cfg2 = config.Config()

assert cfg1 is cfg2, (
"Config() should return same instance (singleton pattern). "
"Got different instances."
)


def test_config_is_dict_like():
"""Verify Config behaves like a dictionary."""
cfg = config.Config()

assert hasattr(cfg, '__getitem__'), "Config must support item access"
assert hasattr(cfg, '__setitem__'), "Config must support item assignment"
assert hasattr(cfg, 'keys'), "Config must support keys()"


def test_fluxscale_exists_and_positive(slitlessutils_config):
"""Verify fluxscale parameter exists and is positive."""
assert 'fluxscale' in slitlessutils_config or hasattr(slitlessutils_config, 'fluxscale'), (
"fluxscale parameter must exist in Config"
)

fluxscale = slitlessutils_config.fluxscale
assert fluxscale is not None, "fluxscale must not be None"
assert np.allclose(fluxscale, 1e-17, rtol=0.01), f"fluxscale value changed, got {fluxscale}"


def test_fluxunits_exists(slitlessutils_config):
"""Verify fluxunits parameter exists."""
assert 'fluxunits' in slitlessutils_config or hasattr(slitlessutils_config, 'fluxunits'), (
"fluxunits parameter must exist in Config"
)

fluxunits = slitlessutils_config.fluxunits
assert fluxunits is not None, "fluxunits must not be None"
assert isinstance(fluxunits, str), f"fluxunits must be a string, got {type(fluxunits)}"


def test_compression_settings_exist(slitlessutils_config):
"""Verify HDF5 compression settings exist."""
assert hasattr(slitlessutils_config, 'compression'), "compression setting must exist"
assert hasattr(slitlessutils_config, 'compression_opts'), "compression_opts setting must exist"


def test_refpath_exists(slitlessutils_config):
"""Verify reference file path is set."""
assert hasattr(slitlessutils_config, 'refpath'), "refpath attribute must exist"
refpath = slitlessutils_config.refpath
assert refpath is not None, "refpath must not be None"
assert isinstance(refpath, str), f"refpath must be string, got {type(refpath)}"


def test_refpath_directory_exists(slitlessutils_config):
"""Verify reference file directory exists on disk."""
refpath = slitlessutils_config.refpath
assert os.path.isdir(refpath), f"Reference path directory must exist: {refpath}"


def test_instruments_directory_exists(slitlessutils_config):
"""Verify instruments subdirectory exists."""
instruments_path = os.path.join(slitlessutils_config.refpath, 'instruments')
assert os.path.isdir(instruments_path), f"Instruments directory must exist: {instruments_path}"


def test_suffixes_defined():
"""Verify standard file suffixes are defined."""
from slitlessutils.config import SUFFIXES

expected_keys = ['1d spectra', '2d spectra', 'L-curve', 'group']

for key in expected_keys:
assert key in SUFFIXES, f"SUFFIXES must include '{key}'"


def test_suffixes_are_strings():
"""Verify all suffixes are non-empty strings."""
from slitlessutils.config import SUFFIXES

for key, suffix in SUFFIXES.items():
assert isinstance(suffix, str), f"Suffix for '{key}' must be string, got {type(suffix)}"
assert len(suffix) > 0, f"Suffix for '{key}' must not be empty"
Loading
Loading