199 lines
8.5 KiB
TypeScript
199 lines
8.5 KiB
TypeScript
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
|
import {
|
|
ConflictException,
|
|
CreateNamespaceCommand,
|
|
CreateTableBucketCommand,
|
|
CreateTableCommand,
|
|
DeleteNamespaceCommand,
|
|
DeleteTableBucketCommand,
|
|
DeleteTableBucketPolicyCommand,
|
|
DeleteTableCommand,
|
|
DeleteTablePolicyCommand,
|
|
GetNamespaceCommand,
|
|
GetTableBucketCommand,
|
|
GetTableBucketMaintenanceConfigurationCommand,
|
|
GetTableBucketPolicyCommand,
|
|
GetTableCommand,
|
|
GetTableMaintenanceConfigurationCommand,
|
|
GetTableMetadataLocationCommand,
|
|
GetTablePolicyCommand,
|
|
ListNamespacesCommand,
|
|
ListTableBucketsCommand,
|
|
ListTablesCommand,
|
|
NotFoundException,
|
|
PutTableBucketMaintenanceConfigurationCommand,
|
|
PutTableBucketPolicyCommand,
|
|
PutTableMaintenanceConfigurationCommand,
|
|
PutTablePolicyCommand,
|
|
RenameTableCommand,
|
|
S3TablesClient,
|
|
UpdateTableMetadataLocationCommand,
|
|
} from '@aws-sdk/client-s3tables';
|
|
import { makeClient, uniqueName } from './setup';
|
|
|
|
describe('S3 Tables lifecycle', () => {
|
|
let client: S3TablesClient;
|
|
let bucketArn: string;
|
|
const bucketName = uniqueName('table-bucket');
|
|
const namespace = 'analytics';
|
|
let tableName = uniqueName('events');
|
|
let versionToken: string;
|
|
|
|
beforeAll(() => {
|
|
client = makeClient(S3TablesClient);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
if (!bucketArn) return;
|
|
await client.send(new DeleteTableCommand({ tableBucketARN: bucketArn, namespace, name: tableName })).catch(() => undefined);
|
|
await client.send(new DeleteNamespaceCommand({ tableBucketARN: bucketArn, namespace })).catch(() => undefined);
|
|
await client.send(new DeleteTableBucketCommand({ tableBucketARN: bucketArn })).catch(() => undefined);
|
|
});
|
|
|
|
it('creates a table bucket, namespace, and Iceberg table', async () => {
|
|
const bucket = await client.send(new CreateTableBucketCommand({
|
|
name: bucketName,
|
|
tags: { suite: 'sdk-node' },
|
|
encryptionConfiguration: { sseAlgorithm: 'AES256' },
|
|
storageClassConfiguration: { storageClass: 'STANDARD' },
|
|
}));
|
|
bucketArn = bucket.arn!;
|
|
expect(bucketArn).toContain(`bucket/${bucketName}`);
|
|
|
|
const createdNamespace = await client.send(new CreateNamespaceCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace: [namespace],
|
|
}));
|
|
expect(createdNamespace.namespace).toEqual([namespace]);
|
|
|
|
const table = await client.send(new CreateTableCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
format: 'ICEBERG',
|
|
metadata: { iceberg: { properties: { 'format-version': '2' } } },
|
|
encryptionConfiguration: { sseAlgorithm: 'AES256' },
|
|
storageClassConfiguration: { storageClass: 'STANDARD' },
|
|
tags: { suite: 'sdk-node' },
|
|
}));
|
|
versionToken = table.versionToken!;
|
|
expect(table.tableARN).toBe(`${bucketArn}/table/${tableName}`);
|
|
});
|
|
|
|
it('gets resources and applies list prefixes', async () => {
|
|
const bucket = await client.send(new GetTableBucketCommand({ tableBucketARN: bucketArn }));
|
|
expect(bucket.name).toBe(bucketName);
|
|
|
|
const buckets = await client.send(new ListTableBucketsCommand({ prefix: 'table-bucket' }));
|
|
expect(buckets.tableBuckets?.some((item) => item.name === bucketName)).toBe(true);
|
|
|
|
const fetchedNamespace = await client.send(new GetNamespaceCommand({ tableBucketARN: bucketArn, namespace }));
|
|
expect(fetchedNamespace.namespace).toEqual([namespace]);
|
|
const namespaces = await client.send(new ListNamespacesCommand({ tableBucketARN: bucketArn, prefix: 'anal' }));
|
|
expect(namespaces.namespaces?.some((item) => item.namespace?.includes(namespace))).toBe(true);
|
|
|
|
const tables = await client.send(new ListTablesCommand({ tableBucketARN: bucketArn, namespace, prefix: 'events' }));
|
|
expect(tables.tables?.some((table) => table.name === tableName)).toBe(true);
|
|
const table = await client.send(new GetTableCommand({ tableBucketARN: bucketArn, namespace, name: tableName }));
|
|
expect(table.tableARN).toBe(`${bucketArn}/table/${tableName}`);
|
|
});
|
|
|
|
it('round-trips policies and metadata version tokens', async () => {
|
|
const policy = '{"Version":"2012-10-17","Statement":[]}';
|
|
await client.send(new PutTableBucketPolicyCommand({ tableBucketARN: bucketArn, resourcePolicy: policy }));
|
|
expect((await client.send(new GetTableBucketPolicyCommand({ tableBucketARN: bucketArn }))).resourcePolicy).toBe(policy);
|
|
await client.send(new PutTablePolicyCommand({ tableBucketARN: bucketArn, namespace, name: tableName, resourcePolicy: policy }));
|
|
expect((await client.send(new GetTablePolicyCommand({ tableBucketARN: bucketArn, namespace, name: tableName }))).resourcePolicy).toBe(policy);
|
|
|
|
const updated = await client.send(new UpdateTableMetadataLocationCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
metadataLocation: `s3://warehouse/${tableName}/v2.metadata.json`,
|
|
versionToken,
|
|
}));
|
|
expect(updated.versionToken).not.toBe(versionToken);
|
|
expect((await client.send(new GetTableMetadataLocationCommand({
|
|
tableBucketARN: bucketArn, namespace, name: tableName,
|
|
}))).metadataLocation).toBe(updated.metadataLocation);
|
|
|
|
await expect(client.send(new UpdateTableMetadataLocationCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
metadataLocation: `s3://warehouse/${tableName}/stale.metadata.json`,
|
|
versionToken,
|
|
}))).rejects.toBeInstanceOf(ConflictException);
|
|
versionToken = updated.versionToken!;
|
|
});
|
|
|
|
it('round-trips typed maintenance configuration maps', async () => {
|
|
await client.send(new PutTableBucketMaintenanceConfigurationCommand({
|
|
tableBucketARN: bucketArn,
|
|
type: 'icebergUnreferencedFileRemoval',
|
|
value: { status: 'enabled' },
|
|
}));
|
|
const bucketMaintenance = await client.send(new GetTableBucketMaintenanceConfigurationCommand({
|
|
tableBucketARN: bucketArn,
|
|
}));
|
|
expect(bucketMaintenance.configuration?.icebergUnreferencedFileRemoval?.status).toBe('enabled');
|
|
|
|
await client.send(new PutTableMaintenanceConfigurationCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
type: 'icebergCompaction',
|
|
value: { status: 'enabled' },
|
|
}));
|
|
const tableMaintenance = await client.send(new GetTableMaintenanceConfigurationCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
}));
|
|
expect(tableMaintenance.configuration?.icebergCompaction?.status).toBe('enabled');
|
|
});
|
|
|
|
it('renames a table and rotates its version token', async () => {
|
|
const originalToken = versionToken;
|
|
const renamedTableName = `${tableName}-renamed`;
|
|
await client.send(new RenameTableCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: tableName,
|
|
newName: renamedTableName,
|
|
versionToken: originalToken,
|
|
}));
|
|
|
|
const renamed = await client.send(new GetTableCommand({
|
|
tableBucketARN: bucketArn,
|
|
namespace,
|
|
name: renamedTableName,
|
|
}));
|
|
expect(renamed.name).toBe(renamedTableName);
|
|
expect(renamed.versionToken).not.toBe(originalToken);
|
|
await expect(client.send(new GetTableCommand({ tableBucketARN: bucketArn, namespace, name: tableName })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
tableName = renamedTableName;
|
|
versionToken = renamed.versionToken!;
|
|
});
|
|
|
|
it('deletes policies and the resource hierarchy', async () => {
|
|
await client.send(new DeleteTablePolicyCommand({ tableBucketARN: bucketArn, namespace, name: tableName }));
|
|
await expect(client.send(new GetTablePolicyCommand({ tableBucketARN: bucketArn, namespace, name: tableName })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
await client.send(new DeleteTableBucketPolicyCommand({ tableBucketARN: bucketArn }));
|
|
await expect(client.send(new GetTableBucketPolicyCommand({ tableBucketARN: bucketArn })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
|
|
await client.send(new DeleteTableCommand({ tableBucketARN: bucketArn, namespace, name: tableName }));
|
|
await expect(client.send(new GetTableCommand({ tableBucketARN: bucketArn, namespace, name: tableName })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
await client.send(new DeleteNamespaceCommand({ tableBucketARN: bucketArn, namespace }));
|
|
await expect(client.send(new GetNamespaceCommand({ tableBucketARN: bucketArn, namespace })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
await client.send(new DeleteTableBucketCommand({ tableBucketARN: bucketArn }));
|
|
await expect(client.send(new GetTableBucketCommand({ tableBucketARN: bucketArn })))
|
|
.rejects.toBeInstanceOf(NotFoundException);
|
|
});
|
|
});
|