447 lines
16 KiB
Python
447 lines
16 KiB
Python
import boto3
|
|
from botocore.exceptions import ClientError
|
|
import sure # noqa # pylint: disable=unused-import
|
|
from datetime import datetime
|
|
import pytest
|
|
|
|
from moto import mock_dynamodb, settings
|
|
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
|
from moto.dynamodb.models import dynamodb_backends
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_standard():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="messages",
|
|
KeySchema=[
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
],
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "id", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5},
|
|
)
|
|
actual = client.describe_table(TableName="messages")["Table"]
|
|
|
|
actual.should.have.key("AttributeDefinitions").equal(
|
|
[
|
|
{"AttributeName": "id", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
]
|
|
)
|
|
actual.should.have.key("CreationDateTime").be.a(datetime)
|
|
actual.should.have.key("GlobalSecondaryIndexes").equal([])
|
|
actual.should.have.key("LocalSecondaryIndexes").equal([])
|
|
actual.should.have.key("ProvisionedThroughput").equal(
|
|
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
|
|
)
|
|
actual.should.have.key("TableSizeBytes").equal(0)
|
|
actual.should.have.key("TableName").equal("messages")
|
|
actual.should.have.key("TableStatus").equal("ACTIVE")
|
|
actual.should.have.key("TableArn").equal(
|
|
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
|
)
|
|
actual.should.have.key("KeySchema").equal(
|
|
[
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
]
|
|
)
|
|
actual.should.have.key("ItemCount").equal(0)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_local_index():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="messages",
|
|
KeySchema=[
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
],
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "id", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
{"AttributeName": "threads", "AttributeType": "S"},
|
|
],
|
|
LocalSecondaryIndexes=[
|
|
{
|
|
"IndexName": "threads_index",
|
|
"KeySchema": [
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "threads", "KeyType": "RANGE"},
|
|
],
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
}
|
|
],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 5},
|
|
)
|
|
actual = client.describe_table(TableName="messages")["Table"]
|
|
|
|
actual.should.have.key("AttributeDefinitions").equal(
|
|
[
|
|
{"AttributeName": "id", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
{"AttributeName": "threads", "AttributeType": "S"},
|
|
]
|
|
)
|
|
actual.should.have.key("CreationDateTime").be.a(datetime)
|
|
actual.should.have.key("GlobalSecondaryIndexes").equal([])
|
|
actual.should.have.key("LocalSecondaryIndexes").equal(
|
|
[
|
|
{
|
|
"IndexName": "threads_index",
|
|
"KeySchema": [
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "threads", "KeyType": "RANGE"},
|
|
],
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
}
|
|
]
|
|
)
|
|
actual.should.have.key("ProvisionedThroughput").equal(
|
|
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 1, "WriteCapacityUnits": 5}
|
|
)
|
|
actual.should.have.key("TableSizeBytes").equal(0)
|
|
actual.should.have.key("TableName").equal("messages")
|
|
actual.should.have.key("TableStatus").equal("ACTIVE")
|
|
actual.should.have.key("TableArn").equal(
|
|
f"arn:aws:dynamodb:us-east-1:{ACCOUNT_ID}:table/messages"
|
|
)
|
|
actual.should.have.key("KeySchema").equal(
|
|
[
|
|
{"AttributeName": "id", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
]
|
|
)
|
|
actual.should.have.key("ItemCount").equal(0)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_gsi():
|
|
dynamodb = boto3.client("dynamodb", region_name="us-east-1")
|
|
|
|
table = dynamodb.create_table(
|
|
TableName="users",
|
|
KeySchema=[
|
|
{"AttributeName": "forum_name", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
],
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "forum_name", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
GlobalSecondaryIndexes=[
|
|
{
|
|
"IndexName": "test_gsi",
|
|
"KeySchema": [{"AttributeName": "subject", "KeyType": "HASH"}],
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
}
|
|
],
|
|
)
|
|
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
|
|
[
|
|
{
|
|
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
|
|
"IndexName": "test_gsi",
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
"IndexStatus": "ACTIVE",
|
|
"ProvisionedThroughput": {
|
|
"ReadCapacityUnits": 0,
|
|
"WriteCapacityUnits": 0,
|
|
},
|
|
}
|
|
]
|
|
)
|
|
|
|
table = dynamodb.create_table(
|
|
TableName="users2",
|
|
KeySchema=[
|
|
{"AttributeName": "forum_name", "KeyType": "HASH"},
|
|
{"AttributeName": "subject", "KeyType": "RANGE"},
|
|
],
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "forum_name", "AttributeType": "S"},
|
|
{"AttributeName": "subject", "AttributeType": "S"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
GlobalSecondaryIndexes=[
|
|
{
|
|
"IndexName": "test_gsi",
|
|
"KeySchema": [{"AttributeName": "subject", "KeyType": "HASH"}],
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
"ProvisionedThroughput": {
|
|
"ReadCapacityUnits": 3,
|
|
"WriteCapacityUnits": 5,
|
|
},
|
|
}
|
|
],
|
|
)
|
|
table["TableDescription"]["GlobalSecondaryIndexes"].should.equal(
|
|
[
|
|
{
|
|
"KeySchema": [{"KeyType": "HASH", "AttributeName": "subject"}],
|
|
"IndexName": "test_gsi",
|
|
"Projection": {"ProjectionType": "ALL"},
|
|
"IndexStatus": "ACTIVE",
|
|
"ProvisionedThroughput": {
|
|
"ReadCapacityUnits": 3,
|
|
"WriteCapacityUnits": 5,
|
|
},
|
|
}
|
|
]
|
|
)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_stream_specification():
|
|
conn = boto3.client("dynamodb", region_name="us-east-1")
|
|
|
|
resp = conn.create_table(
|
|
TableName="test-streams",
|
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
|
|
StreamSpecification={
|
|
"StreamEnabled": True,
|
|
"StreamViewType": "NEW_AND_OLD_IMAGES",
|
|
},
|
|
)
|
|
|
|
resp["TableDescription"].should.have.key("StreamSpecification")
|
|
resp["TableDescription"]["StreamSpecification"].should.equal(
|
|
{"StreamEnabled": True, "StreamViewType": "NEW_AND_OLD_IMAGES"}
|
|
)
|
|
resp["TableDescription"].should.contain("LatestStreamLabel")
|
|
resp["TableDescription"].should.contain("LatestStreamArn")
|
|
|
|
resp = conn.delete_table(TableName="test-streams")
|
|
|
|
resp["TableDescription"].should.contain("StreamSpecification")
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_tags():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
|
|
resp = client.create_table(
|
|
TableName="test-streams",
|
|
KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
|
|
AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
|
|
Tags=[{"Key": "tk", "Value": "tv"}],
|
|
)
|
|
|
|
resp = client.list_tags_of_resource(
|
|
ResourceArn=resp["TableDescription"]["TableArn"]
|
|
)
|
|
resp.should.have.key("Tags").equals([{"Key": "tk", "Value": "tv"}])
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_pay_per_request():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
)
|
|
|
|
actual = client.describe_table(TableName="test1")["Table"]
|
|
actual.should.have.key("BillingModeSummary").equals(
|
|
{"BillingMode": "PAY_PER_REQUEST"}
|
|
)
|
|
actual.should.have.key("ProvisionedThroughput").equals(
|
|
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 0, "WriteCapacityUnits": 0}
|
|
)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table__provisioned_throughput():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 2, "WriteCapacityUnits": 3},
|
|
)
|
|
|
|
actual = client.describe_table(TableName="test1")["Table"]
|
|
actual.should.have.key("BillingModeSummary").equals({"BillingMode": "PROVISIONED"})
|
|
actual.should.have.key("ProvisionedThroughput").equals(
|
|
{"NumberOfDecreasesToday": 0, "ReadCapacityUnits": 2, "WriteCapacityUnits": 3}
|
|
)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_without_specifying_throughput():
|
|
dynamodb_client = boto3.client("dynamodb", region_name="us-east-1")
|
|
|
|
with pytest.raises(ClientError) as exc:
|
|
dynamodb_client.create_table(
|
|
TableName="my-table",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "some_field", "AttributeType": "S"}
|
|
],
|
|
KeySchema=[{"AttributeName": "some_field", "KeyType": "HASH"}],
|
|
BillingMode="PROVISIONED",
|
|
StreamSpecification={"StreamEnabled": False, "StreamViewType": "NEW_IMAGE"},
|
|
)
|
|
err = exc.value.response["Error"]
|
|
err["Code"].should.equal("ValidationException")
|
|
err["Message"].should.equal(
|
|
"One or more parameter values were invalid: ReadCapacityUnits and WriteCapacityUnits must both be specified when BillingMode is PROVISIONED"
|
|
)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_error_pay_per_request_with_provisioned_param():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
|
|
with pytest.raises(ClientError) as exc:
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
|
|
BillingMode="PAY_PER_REQUEST",
|
|
)
|
|
err = exc.value.response["Error"]
|
|
err["Code"].should.equal("ValidationException")
|
|
err["Message"].should.equal(
|
|
"ProvisionedThroughput cannot be specified when BillingMode is PAY_PER_REQUEST"
|
|
)
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_ssespecification__false():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
SSESpecification={"Enabled": False},
|
|
)
|
|
|
|
actual = client.describe_table(TableName="test1")["Table"]
|
|
actual.shouldnt.have.key("SSEDescription")
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_ssespecification__true():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
SSESpecification={"Enabled": True},
|
|
)
|
|
|
|
actual = client.describe_table(TableName="test1")["Table"]
|
|
actual.should.have.key("SSEDescription")
|
|
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
|
|
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
|
|
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").match(
|
|
"^arn:aws:kms"
|
|
) # Default KMS key for DynamoDB
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table_with_ssespecification__custom_kms_key():
|
|
client = boto3.client("dynamodb", region_name="us-east-1")
|
|
client.create_table(
|
|
TableName="test1",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "client", "AttributeType": "S"},
|
|
{"AttributeName": "app", "AttributeType": "S"},
|
|
],
|
|
KeySchema=[
|
|
{"AttributeName": "client", "KeyType": "HASH"},
|
|
{"AttributeName": "app", "KeyType": "RANGE"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
SSESpecification={"Enabled": True, "KMSMasterKeyId": "custom-kms-key"},
|
|
)
|
|
|
|
actual = client.describe_table(TableName="test1")["Table"]
|
|
actual.should.have.key("SSEDescription")
|
|
actual["SSEDescription"].should.have.key("Status").equals("ENABLED")
|
|
actual["SSEDescription"].should.have.key("SSEType").equals("KMS")
|
|
actual["SSEDescription"].should.have.key("KMSMasterKeyArn").equals("custom-kms-key")
|
|
|
|
|
|
@mock_dynamodb
|
|
def test_create_table__specify_non_key_column():
|
|
client = boto3.client("dynamodb", "us-east-2")
|
|
client.create_table(
|
|
TableName="tab",
|
|
KeySchema=[
|
|
{"AttributeName": "PK", "KeyType": "HASH"},
|
|
{"AttributeName": "SomeColumn", "KeyType": "N"},
|
|
],
|
|
BillingMode="PAY_PER_REQUEST",
|
|
AttributeDefinitions=[
|
|
{"AttributeName": "PK", "AttributeType": "S"},
|
|
{"AttributeName": "SomeColumn", "AttributeType": "N"},
|
|
],
|
|
)
|
|
|
|
actual = client.describe_table(TableName="tab")["Table"]
|
|
actual["KeySchema"].should.equal(
|
|
[
|
|
{"AttributeName": "PK", "KeyType": "HASH"},
|
|
{"AttributeName": "SomeColumn", "KeyType": "N"},
|
|
]
|
|
)
|
|
|
|
if not settings.TEST_SERVER_MODE:
|
|
ddb = dynamodb_backends[ACCOUNT_ID]["us-east-2"]
|
|
ddb.tables["tab"].attr.should.contain(
|
|
{"AttributeName": "PK", "AttributeType": "S"}
|
|
)
|
|
ddb.tables["tab"].attr.should.contain(
|
|
{"AttributeName": "SomeColumn", "AttributeType": "N"}
|
|
)
|
|
# It should recognize PK is the Hash Key
|
|
ddb.tables["tab"].hash_key_attr.should.equal("PK")
|
|
# It should recognize that SomeColumn is not a Range Key
|
|
ddb.tables["tab"].has_range_key.should.equal(False)
|
|
ddb.tables["tab"].range_key_names.should.equal([])
|