◐ Shell
clean mode source ↗

feat(storage): full object checksum: implement rolling checksum and v… · googleapis/google-cloud-python@2361ba6

@@ -45,6 +45,48 @@ def test_initialization(self):

4545

self.assertEqual(state.bytes_written, 0)

4646

self.assertEqual(state.next_expected_offset, initial_offset)

4747

self.assertFalse(state.is_complete)

48+

self.assertFalse(state.is_full_object_read)

49+

self.assertIsNone(state.rolling_checksum)

50+51+

def test_initialization_with_full_object_read(self):

52+

"""Test that _DownloadState initializes correctly when is_full_object_read is True."""

53+

initial_offset = 10

54+

initial_length = 100

55+

user_buffer = io.BytesIO()

56+

state_full = _DownloadState(

57+

initial_offset, initial_length, user_buffer, is_full_object_read=True

58+

)

59+60+

self.assertEqual(state_full.initial_offset, initial_offset)

61+

self.assertEqual(state_full.initial_length, initial_length)

62+

self.assertEqual(state_full.user_buffer, user_buffer)

63+

self.assertEqual(state_full.bytes_written, 0)

64+

self.assertEqual(state_full.next_expected_offset, initial_offset)

65+

self.assertFalse(state_full.is_complete)

66+

self.assertTrue(state_full.is_full_object_read)

67+

self.assertIsNotNone(state_full.rolling_checksum)

68+69+

def test_initialization_with_full_object_read_and_checksum_disabled(self):

70+

"""Test that _DownloadState does not initialize rolling_checksum when enable_checksum is False."""

71+

initial_offset = 10

72+

initial_length = 100

73+

user_buffer = io.BytesIO()

74+

state_full = _DownloadState(

75+

initial_offset,

76+

initial_length,

77+

user_buffer,

78+

is_full_object_read=True,

79+

enable_checksum=False,

80+

)

81+82+

self.assertEqual(state_full.initial_offset, initial_offset)

83+

self.assertEqual(state_full.initial_length, initial_length)

84+

self.assertEqual(state_full.user_buffer, user_buffer)

85+

self.assertEqual(state_full.bytes_written, 0)

86+

self.assertEqual(state_full.next_expected_offset, initial_offset)

87+

self.assertFalse(state_full.is_complete)

88+

self.assertTrue(state_full.is_full_object_read)

89+

self.assertIsNone(state_full.rolling_checksum)

489049915092

class TestReadResumptionStrategy(unittest.TestCase):

@@ -53,12 +95,24 @@ def setUp(self):

53955496

self.state = {"download_states": {}, "read_handle": None, "routing_token": None}

559756-

def _add_download(self, read_id, offset=0, length=100, buffer=None):

98+

def _add_download(

99+

self,

100+

read_id,

101+

offset=0,

102+

length=100,

103+

buffer=None,

104+

is_full_object_read=False,

105+

enable_checksum=True,

106+

):

57107

"""Helper to inject a download state into the correct nested location."""

58108

if buffer is None:

59109

buffer = io.BytesIO()

60110

state = _DownloadState(

61-

initial_offset=offset, initial_length=length, user_buffer=buffer

111+

initial_offset=offset,

112+

initial_length=length,

113+

user_buffer=buffer,

114+

is_full_object_read=is_full_object_read,

115+

enable_checksum=enable_checksum,

62116

)

63117

self.state["download_states"][read_id] = state

64118

return state

@@ -358,3 +412,61 @@ async def run():

358412359413

# Token should remain unchanged

360414

self.assertEqual(self.state["routing_token"], "existing-token")

415+416+

def test_update_state_full_object_checksum_success(self):

417+

"""Test that full object checksum verification succeeds on range_end."""

418+

read_state = self._add_download(

419+

_READ_ID, offset=0, length=9, is_full_object_read=True

420+

)

421+

self.state["enable_checksum"] = True

422+

self.state["full_obj_server_crc32c"] = google_crc32c.value(b"testdata1")

423+424+

resp1 = self._create_response(b"test", _READ_ID, offset=0)

425+

self.strategy.update_state_from_response(resp1, self.state)

426+427+

resp2 = self._create_response(b"data1", _READ_ID, offset=4, range_end=True)

428+

self.strategy.update_state_from_response(resp2, self.state)

429+430+

self.assertTrue(read_state.is_complete)

431+

self.assertEqual(read_state.bytes_written, 9)

432+433+

def test_update_state_full_object_checksum_failure(self):

434+

"""Test that full object checksum verification raises DataCorruption on mismatch at range_end."""

435+

self._add_download(_READ_ID, offset=0, length=9, is_full_object_read=True)

436+

self.state["enable_checksum"] = True

437+

self.state["full_obj_server_crc32c"] = 111111 # Wrong server checksum!

438+439+

resp1 = self._create_response(b"test", _READ_ID, offset=0)

440+

self.strategy.update_state_from_response(resp1, self.state)

441+442+

resp2 = self._create_response(b"data1", _READ_ID, offset=4, range_end=True)

443+

with self.assertRaisesRegex(DataCorruption, "Full object checksum mismatch"):

444+

self.strategy.update_state_from_response(resp2, self.state)

445+446+

def test_update_state_checksum_mismatch_ignored_when_disabled(self):

447+

"""Test that a CRC32C mismatch is ignored when enable_checksum is False."""

448+

self._add_download(_READ_ID)

449+

self.state["enable_checksum"] = False

450+

response = self._create_response(b"data", _READ_ID, offset=0, crc=999999)

451+452+

# Should NOT raise DataCorruption!

453+

self.strategy.update_state_from_response(response, self.state)

454+455+

def test_update_state_full_object_checksum_mismatch_ignored_when_disabled(self):

456+

"""Test that a full-object CRC32C mismatch is ignored when enable_checksum is False."""

457+

self._add_download(

458+

_READ_ID,

459+

offset=0,

460+

length=9,

461+

is_full_object_read=True,

462+

enable_checksum=False,

463+

)

464+

self.state["enable_checksum"] = False

465+

self.state["full_obj_server_crc32c"] = 111111 # Wrong server checksum!

466+467+

resp1 = self._create_response(b"test", _READ_ID, offset=0)

468+

self.strategy.update_state_from_response(resp1, self.state)

469+470+

resp2 = self._create_response(b"data1", _READ_ID, offset=4, range_end=True)

471+

# Should NOT raise DataCorruption!

472+

self.strategy.update_state_from_response(resp2, self.state)