package s3tables import ( "context" "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func updateTableRequest(t *testing.T, metadataLocation string, version int) *UpdateTableRequest { t.Helper() return &UpdateTableRequest{ TableBucketARN: mustBucketARN(t), Namespace: []string{"ns"}, Name: "t", MetadataVersion: version, MetadataLocation: metadataLocation, } } // A second commit that reads the same base must not overwrite the first one's // metadata pointer: the snapshot the first commit published would be lost. func TestUpdateTableRejectsLostUpdate(t *testing.T) { fs, m := startRenameManager(t) winnerLocation := "s3://" + renameTestBucket + "/ns/t/metadata/v4-winner.metadata.json" fs.BeforeUpdate = func() { winner, err := json.Marshal(tableMetadataInternal{ Name: "t", Namespace: "ns", Format: "ICEBERG", OwnerAccountID: DefaultAccountID, MetadataVersion: 4, MetadataLocation: winnerLocation, VersionToken: generateVersionToken(), }) require.NoError(t, err) entry := fs.Get(GetNamespacePath(renameTestBucket, "ns"), "t") require.NotNil(t, entry) entry.Extended[ExtendedKeyMetadata] = winner } loserLocation := "s3://" + renameTestBucket + "/ns/t/metadata/v4-loser.metadata.json" err := m.Execute(context.Background(), NewManagerClient(fs.Client), "UpdateTable", updateTableRequest(t, loserLocation, 4), nil, "") require.Error(t, err) var s3Err *S3TablesError require.ErrorAs(t, err, &s3Err) assert.Equal(t, ErrCodeConflict, s3Err.Type) got, err := runGetTable(t, m, fs, "ns", "t") require.NoError(t, err) assert.Equal(t, winnerLocation, got.MetadataLocation, "the first commit must survive") } // A policy written between the authorization check and the write decides // whether this caller may still commit, so the commit is rejected rather than // applied on a decision that has been overtaken - and the policy itself must // survive, not be reverted by the commit's stale copy of the entry. func TestUpdateTableRejectsWhenTheAuthorizingPolicyChanges(t *testing.T) { fs, m := startRenameManager(t) policy := []byte(`{"Version":"2012-10-17","Statement":[]}`) fs.BeforeUpdate = func() { entry := fs.Get(GetNamespacePath(renameTestBucket, "ns"), "t") require.NotNil(t, entry) entry.Extended[ExtendedKeyPolicy] = policy } location := "s3://" + renameTestBucket + "/ns/t/metadata/v4.metadata.json" err := m.Execute(context.Background(), NewManagerClient(fs.Client), "UpdateTable", updateTableRequest(t, location, 4), nil, "") require.Error(t, err) var s3Err *S3TablesError require.ErrorAs(t, err, &s3Err) assert.Equal(t, ErrCodeConflict, s3Err.Type) entry := fs.Get(GetNamespacePath(renameTestBucket, "ns"), "t") require.NotNil(t, entry) assert.Equal(t, policy, entry.Extended[ExtendedKeyPolicy], "the concurrent policy write must survive") } func TestUpdateTableAppliesWithoutContention(t *testing.T) { fs, m := startRenameManager(t) location := "s3://" + renameTestBucket + "/ns/t/metadata/v4.metadata.json" require.NoError(t, m.Execute(context.Background(), NewManagerClient(fs.Client), "UpdateTable", updateTableRequest(t, location, 4), nil, "")) got, err := runGetTable(t, m, fs, "ns", "t") require.NoError(t, err) assert.Equal(t, location, got.MetadataLocation) assert.Equal(t, 4, got.MetadataVersion) }