Coverage for tests/test_fits_format_version.py: 95%

63 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-14 09:57 +0000

1# This file is part of lsst-images. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://www.lsst.org). 

6# See the COPYRIGHT file at the top-level directory of this distribution 

7# for details of code ownership. 

8# 

9# Use of this source code is governed by a 3-clause BSD-style 

10# license that can be found in the LICENSE file. 

11 

12from __future__ import annotations 

13 

14from pathlib import Path 

15 

16import astropy.io.fits 

17import numpy as np 

18import pytest 

19 

20from lsst.images import Image 

21from lsst.images.fits import FitsInputArchive 

22from lsst.images.serialization import ArchiveReadError, write_archive 

23 

24 

25def _write_simple_image_fits(path: Path | str) -> None: 

26 """Write a tiny Image to ``path`` via the high-level API.""" 

27 image = Image(0.0, shape=(4, 4), dtype="float32") 

28 write_archive(image, path) 

29 

30 

31def test_write_emits_fmtver_and_datamodl(tmp_path: Path) -> None: 

32 """Verify a freshly-written FITS carries FMTVER=1 and the root DATAMODL.""" 

33 path = tmp_path / "x.fits" 

34 _write_simple_image_fits(path) 

35 with astropy.io.fits.open(path) as hdul: 

36 assert hdul[0].header["FMTVER"] == 1 

37 assert hdul[0].header["DATAMODL"] == "https://images.lsst.io/schemas/image-1.0.0" 

38 

39 

40def test_read_succeeds_when_fmtver_matches(tmp_path: Path) -> None: 

41 """Verify that a round-trip read of a freshly-written file succeeds.""" 

42 path = tmp_path / "x.fits" 

43 _write_simple_image_fits(path) 

44 with FitsInputArchive.open(path): 

45 pass 

46 

47 

48def test_read_fails_when_fmtver_too_high(tmp_path: Path) -> None: 

49 """Verify that a file whose FMTVER is newer than this release raises.""" 

50 path = tmp_path / "x.fits" 

51 _write_simple_image_fits(path) 

52 with astropy.io.fits.open(path, mode="update") as hdul: 

53 hdul[0].header["FMTVER"] = 2 

54 hdul.flush() 

55 with pytest.raises(ArchiveReadError): 

56 with FitsInputArchive.open(path): 

57 pass 

58 

59 

60def test_read_fails_when_fmtver_absent(tmp_path: Path) -> None: 

61 """Verify a file lacking FMTVER is rejected rather than assumed to be v1. 

62 

63 FitsOutputArchive writes FMTVER before anything else, so every file this 

64 reader can open has one; absence means a damaged file, and guessing 1 

65 would guess at the layout. 

66 """ 

67 path = tmp_path / "x.fits" 

68 _write_simple_image_fits(path) 

69 with astropy.io.fits.open(path, mode="update") as hdul: 

70 del hdul[0].header["FMTVER"] 

71 hdul.flush() 

72 with pytest.raises(ArchiveReadError, match="FMTVER"): 

73 with FitsInputArchive.open(path): 

74 pass 

75 

76 

77def test_get_basic_info_fails_when_fmtver_absent(tmp_path: Path) -> None: 

78 """Verify the info-only read requires FMTVER as well.""" 

79 path = tmp_path / "x.fits" 

80 _write_simple_image_fits(path) 

81 with astropy.io.fits.open(path, mode="update") as hdul: 

82 del hdul[0].header["FMTVER"] 

83 hdul.flush() 

84 with pytest.raises(ArchiveReadError, match="FMTVER"): 

85 FitsInputArchive.get_basic_info(path) 

86 

87 

88def test_read_succeeds_when_datamodl_absent(tmp_path: Path) -> None: 

89 """Verify a file with the layout but no schema card still opens. 

90 

91 DATAMODL is informational on read, so only callers that ask for the 

92 schema through ``info`` need it; requiring the layout stamp must not 

93 start requiring this one too. 

94 """ 

95 path = tmp_path / "x.fits" 

96 _write_simple_image_fits(path) 

97 with astropy.io.fits.open(path, mode="update") as hdul: 

98 del hdul[0].header["DATAMODL"] 

99 hdul.flush() 

100 with FitsInputArchive.open(path) as archive: 

101 with pytest.raises(ArchiveReadError, match="DATAMODL"): 

102 archive.info 

103 

104 

105def test_foreign_fits_is_rejected_cleanly(tmp_path: Path) -> None: 

106 """Verify a FITS file this package did not write raises ArchiveReadError. 

107 

108 Such a file has never been readable -- it carries none of the container 

109 cards -- but it used to surface as a bare KeyError from the first card 

110 popped without a default. 

111 """ 

112 path = tmp_path / "plain.fits" 

113 astropy.io.fits.PrimaryHDU(np.zeros((4, 4), dtype="float32")).writeto(path) 

114 with pytest.raises(ArchiveReadError): 

115 with FitsInputArchive.open(path): 

116 pass