second first commit
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
/**
|
||||
* DynamoDB integration tests.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import {
|
||||
DynamoDBClient,
|
||||
CreateTableCommand,
|
||||
PutItemCommand,
|
||||
GetItemCommand,
|
||||
DeleteItemCommand,
|
||||
ScanCommand,
|
||||
QueryCommand,
|
||||
UpdateItemCommand,
|
||||
DeleteTableCommand,
|
||||
ListTablesCommand,
|
||||
DescribeTableCommand,
|
||||
} from '@aws-sdk/client-dynamodb';
|
||||
import { makeClient, uniqueName } from './setup';
|
||||
|
||||
describe('DynamoDB', () => {
|
||||
let dynamo: DynamoDBClient;
|
||||
let tableName: string;
|
||||
|
||||
beforeAll(() => {
|
||||
dynamo = makeClient(DynamoDBClient);
|
||||
tableName = `test-table-${uniqueName()}`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
await dynamo.send(new DeleteTableCommand({ TableName: tableName }));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
it('should create table', async () => {
|
||||
await dynamo.send(
|
||||
new CreateTableCommand({
|
||||
TableName: tableName,
|
||||
KeySchema: [
|
||||
{ AttributeName: 'pk', KeyType: 'HASH' },
|
||||
{ AttributeName: 'sk', KeyType: 'RANGE' },
|
||||
],
|
||||
AttributeDefinitions: [
|
||||
{ AttributeName: 'pk', AttributeType: 'S' },
|
||||
{ AttributeName: 'sk', AttributeType: 'S' },
|
||||
],
|
||||
BillingMode: 'PAY_PER_REQUEST',
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should describe table', async () => {
|
||||
const response = await dynamo.send(new DescribeTableCommand({ TableName: tableName }));
|
||||
expect(response.Table?.TableStatus).toBe('ACTIVE');
|
||||
});
|
||||
|
||||
it('should list tables', async () => {
|
||||
const response = await dynamo.send(new ListTablesCommand({}));
|
||||
expect(response.TableNames?.includes(tableName)).toBe(true);
|
||||
});
|
||||
|
||||
it('should put item', async () => {
|
||||
await dynamo.send(
|
||||
new PutItemCommand({
|
||||
TableName: tableName,
|
||||
Item: {
|
||||
pk: { S: 'user#1' },
|
||||
sk: { S: 'profile' },
|
||||
name: { S: 'Alice' },
|
||||
age: { N: '30' },
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should get item', async () => {
|
||||
const response = await dynamo.send(
|
||||
new GetItemCommand({
|
||||
TableName: tableName,
|
||||
Key: { pk: { S: 'user#1' }, sk: { S: 'profile' } },
|
||||
})
|
||||
);
|
||||
expect(response.Item?.name?.S).toBe('Alice');
|
||||
});
|
||||
|
||||
it('should update item', async () => {
|
||||
await dynamo.send(
|
||||
new UpdateItemCommand({
|
||||
TableName: tableName,
|
||||
Key: { pk: { S: 'user#1' }, sk: { S: 'profile' } },
|
||||
UpdateExpression: 'SET #n = :v',
|
||||
ExpressionAttributeNames: { '#n': 'name' },
|
||||
ExpressionAttributeValues: { ':v': { S: 'Bob' } },
|
||||
})
|
||||
);
|
||||
|
||||
const response = await dynamo.send(
|
||||
new GetItemCommand({
|
||||
TableName: tableName,
|
||||
Key: { pk: { S: 'user#1' }, sk: { S: 'profile' } },
|
||||
})
|
||||
);
|
||||
expect(response.Item?.name?.S).toBe('Bob');
|
||||
});
|
||||
|
||||
it('should put second item', async () => {
|
||||
await dynamo.send(
|
||||
new PutItemCommand({
|
||||
TableName: tableName,
|
||||
Item: {
|
||||
pk: { S: 'user#2' },
|
||||
sk: { S: 'profile' },
|
||||
name: { S: 'Carol' },
|
||||
age: { N: '25' },
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should scan table', async () => {
|
||||
const response = await dynamo.send(new ScanCommand({ TableName: tableName }));
|
||||
expect(response.Count).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it('should query table', async () => {
|
||||
const response = await dynamo.send(
|
||||
new QueryCommand({
|
||||
TableName: tableName,
|
||||
KeyConditionExpression: 'pk = :pk',
|
||||
ExpressionAttributeValues: { ':pk': { S: 'user#1' } },
|
||||
})
|
||||
);
|
||||
expect(response.Count).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it('should delete items', async () => {
|
||||
await dynamo.send(
|
||||
new DeleteItemCommand({
|
||||
TableName: tableName,
|
||||
Key: { pk: { S: 'user#1' }, sk: { S: 'profile' } },
|
||||
})
|
||||
);
|
||||
await dynamo.send(
|
||||
new DeleteItemCommand({
|
||||
TableName: tableName,
|
||||
Key: { pk: { S: 'user#2' }, sk: { S: 'profile' } },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should delete table', async () => {
|
||||
await dynamo.send(new DeleteTableCommand({ TableName: tableName }));
|
||||
tableName = '';
|
||||
});
|
||||
});
|
||||
|
||||
describe('DynamoDB GSI/LSI', () => {
|
||||
let dynamo: DynamoDBClient;
|
||||
let tableName: string;
|
||||
|
||||
beforeAll(() => {
|
||||
dynamo = makeClient(DynamoDBClient);
|
||||
tableName = `test-gsi-table-${uniqueName()}`;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
await dynamo.send(new DeleteTableCommand({ TableName: tableName }));
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
it('should create table with GSI and LSI', async () => {
|
||||
await dynamo.send(
|
||||
new CreateTableCommand({
|
||||
TableName: tableName,
|
||||
KeySchema: [
|
||||
{ AttributeName: 'pk', KeyType: 'HASH' },
|
||||
{ AttributeName: 'sk', KeyType: 'RANGE' },
|
||||
],
|
||||
AttributeDefinitions: [
|
||||
{ AttributeName: 'pk', AttributeType: 'S' },
|
||||
{ AttributeName: 'sk', AttributeType: 'S' },
|
||||
{ AttributeName: 'gsiPk', AttributeType: 'S' },
|
||||
{ AttributeName: 'lsiSk', AttributeType: 'S' },
|
||||
],
|
||||
GlobalSecondaryIndexes: [
|
||||
{
|
||||
IndexName: 'gsi-1',
|
||||
KeySchema: [
|
||||
{ AttributeName: 'gsiPk', KeyType: 'HASH' },
|
||||
{ AttributeName: 'sk', KeyType: 'RANGE' },
|
||||
],
|
||||
Projection: { ProjectionType: 'ALL' },
|
||||
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 },
|
||||
},
|
||||
],
|
||||
LocalSecondaryIndexes: [
|
||||
{
|
||||
IndexName: 'lsi-1',
|
||||
KeySchema: [
|
||||
{ AttributeName: 'pk', KeyType: 'HASH' },
|
||||
{ AttributeName: 'lsiSk', KeyType: 'RANGE' },
|
||||
],
|
||||
Projection: { ProjectionType: 'KEYS_ONLY' },
|
||||
},
|
||||
],
|
||||
ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5 },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should verify indexes via DescribeTable', async () => {
|
||||
const desc = await dynamo.send(new DescribeTableCommand({ TableName: tableName }));
|
||||
const gsis = desc.Table?.GlobalSecondaryIndexes || [];
|
||||
const lsis = desc.Table?.LocalSecondaryIndexes || [];
|
||||
|
||||
expect(gsis.length).toBe(1);
|
||||
expect(gsis[0]?.IndexName).toBe('gsi-1');
|
||||
expect(gsis[0]?.Projection?.ProjectionType).toBe('ALL');
|
||||
expect(lsis.length).toBe(1);
|
||||
expect(lsis[0]?.IndexName).toBe('lsi-1');
|
||||
expect(lsis[0]?.Projection?.ProjectionType).toBe('KEYS_ONLY');
|
||||
});
|
||||
|
||||
it('should put items with GSI attributes', async () => {
|
||||
await dynamo.send(
|
||||
new PutItemCommand({
|
||||
TableName: tableName,
|
||||
Item: {
|
||||
pk: { S: 'item-1' },
|
||||
sk: { S: 'rev-1' },
|
||||
gsiPk: { S: 'group-A' },
|
||||
lsiSk: { S: '2024-01-01' },
|
||||
},
|
||||
})
|
||||
);
|
||||
await dynamo.send(
|
||||
new PutItemCommand({
|
||||
TableName: tableName,
|
||||
Item: {
|
||||
pk: { S: 'item-2' },
|
||||
sk: { S: 'rev-1' },
|
||||
gsiPk: { S: 'group-A' },
|
||||
lsiSk: { S: '2024-01-02' },
|
||||
},
|
||||
})
|
||||
);
|
||||
await dynamo.send(
|
||||
new PutItemCommand({
|
||||
TableName: tableName,
|
||||
Item: { pk: { S: 'item-3' }, sk: { S: 'rev-1' }, data: { S: 'no-gsi-attrs' } },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should query GSI', async () => {
|
||||
const resp = await dynamo.send(
|
||||
new QueryCommand({
|
||||
TableName: tableName,
|
||||
IndexName: 'gsi-1',
|
||||
KeyConditionExpression: 'gsiPk = :gpk',
|
||||
ExpressionAttributeValues: { ':gpk': { S: 'group-A' } },
|
||||
})
|
||||
);
|
||||
expect(resp.Count).toBe(2);
|
||||
const pks = new Set(resp.Items?.map((i) => i.pk.S));
|
||||
expect(pks.has('item-3')).toBe(false);
|
||||
});
|
||||
|
||||
it('should query LSI', async () => {
|
||||
const resp = await dynamo.send(
|
||||
new QueryCommand({
|
||||
TableName: tableName,
|
||||
IndexName: 'lsi-1',
|
||||
KeyConditionExpression: 'pk = :pk AND lsiSk > :d',
|
||||
ExpressionAttributeValues: { ':pk': { S: 'item-1' }, ':d': { S: '2024-01-00' } },
|
||||
})
|
||||
);
|
||||
expect(resp.Count).toBe(1);
|
||||
});
|
||||
|
||||
it('should delete table', async () => {
|
||||
await dynamo.send(new DeleteTableCommand({ TableName: tableName }));
|
||||
tableName = '';
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user