Admin: Add tests for Java SDK (#6002)
This commit is contained in:
parent
c5a91e6cc6
commit
d525423f94
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@ -70,6 +70,35 @@ jobs:
|
|||||||
run:
|
run:
|
||||||
make lint
|
make lint
|
||||||
|
|
||||||
|
javatest:
|
||||||
|
name: Test behaviour for Java SDK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up Python ${{ matrix.python-version }}
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: "3.8"
|
||||||
|
- name: Start MotoServer
|
||||||
|
run: |
|
||||||
|
pip install build
|
||||||
|
python -m build
|
||||||
|
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh &
|
||||||
|
python scripts/ci_wait_for_server.py
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: 'maven'
|
||||||
|
- name: Build with Maven
|
||||||
|
run: |
|
||||||
|
mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
|
||||||
|
cd other_langs/tests_java && mvn test
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Unit test
|
name: Unit test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -182,7 +211,7 @@ jobs:
|
|||||||
deploy:
|
deploy:
|
||||||
name: Deploy
|
name: Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [test, testserver ]
|
needs: [javatest, test, testserver ]
|
||||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'getmoto/moto' }}
|
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.repository == 'getmoto/moto' }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,3 +30,4 @@ htmlcov/
|
|||||||
docs/_build
|
docs/_build
|
||||||
moto_recording
|
moto_recording
|
||||||
.hypothesis
|
.hypothesis
|
||||||
|
other_langs/tests_java/target
|
||||||
|
@ -71,10 +71,14 @@ class RESTError(HTTPException):
|
|||||||
self, *args: Any, **kwargs: Any # pylint: disable=unused-argument
|
self, *args: Any, **kwargs: Any # pylint: disable=unused-argument
|
||||||
) -> List[Tuple[str, str]]:
|
) -> List[Tuple[str, str]]:
|
||||||
return [
|
return [
|
||||||
("X-Amzn-ErrorType", self.error_type or "UnknownError"),
|
("X-Amzn-ErrorType", self.relative_error_type or "UnknownError"),
|
||||||
("Content-Type", self.content_type),
|
("Content-Type", self.content_type),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relative_error_type(self) -> str:
|
||||||
|
return self.error_type
|
||||||
|
|
||||||
def get_body(
|
def get_body(
|
||||||
self, *args: Any, **kwargs: Any # pylint: disable=unused-argument
|
self, *args: Any, **kwargs: Any # pylint: disable=unused-argument
|
||||||
) -> str:
|
) -> str:
|
||||||
@ -95,6 +99,12 @@ class JsonRESTError(RESTError):
|
|||||||
)
|
)
|
||||||
self.content_type = "application/json"
|
self.content_type = "application/json"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relative_error_type(self) -> str:
|
||||||
|
# https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html
|
||||||
|
# If a # character is present, then take only the contents after the first # character in the value
|
||||||
|
return self.error_type.split("#")[-1]
|
||||||
|
|
||||||
def get_body(self, *args: Any, **kwargs: Any) -> str:
|
def get_body(self, *args: Any, **kwargs: Any) -> str:
|
||||||
return self.description
|
return self.description
|
||||||
|
|
||||||
|
@ -315,9 +315,9 @@ class TableAlreadyExistsException(JsonRESTError):
|
|||||||
|
|
||||||
|
|
||||||
class ResourceInUseException(JsonRESTError):
|
class ResourceInUseException(JsonRESTError):
|
||||||
def __init__(self) -> None:
|
def __init__(self, msg: Optional[str] = None) -> None:
|
||||||
er = ERROR_TYPE_PREFIX + "ResourceInUseException"
|
er = ERROR_TYPE_PREFIX + "ResourceInUseException"
|
||||||
super().__init__(er, "Resource in use")
|
super().__init__(er, msg or "Resource in use")
|
||||||
|
|
||||||
|
|
||||||
class StreamAlreadyEnabledException(JsonRESTError):
|
class StreamAlreadyEnabledException(JsonRESTError):
|
||||||
|
@ -60,7 +60,7 @@ class DynamoDBBackend(BaseBackend):
|
|||||||
|
|
||||||
def create_table(self, name: str, **params: Any) -> Table:
|
def create_table(self, name: str, **params: Any) -> Table:
|
||||||
if name in self.tables:
|
if name in self.tables:
|
||||||
raise ResourceInUseException
|
raise ResourceInUseException(f"Table already exists: {name}")
|
||||||
table = Table(
|
table = Table(
|
||||||
name, account_id=self.account_id, region=self.region_name, **params
|
name, account_id=self.account_id, region=self.region_name, **params
|
||||||
)
|
)
|
||||||
|
112
other_langs/tests_java/pom.xml
Normal file
112
other_langs/tests_java/pom.xml
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>moto.tests</groupId>
|
||||||
|
<artifactId>java-tests</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>java-tests</name>
|
||||||
|
<url>docs.getmoto.org</url>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<maven.compiler.source>1.8</maven.compiler.source>
|
||||||
|
<maven.compiler.target>1.8</maven.compiler.target>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>bom</artifactId>
|
||||||
|
<version>2.20.14</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<version>4.11</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>dynamodb</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>s3</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>netty-nio-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>apache-client</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>url-connection-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>apache-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
|
||||||
|
<plugins>
|
||||||
|
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-clean-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>2.22.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
<version>3.0.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
|
<version>2.5.2</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-deploy-plugin</artifactId>
|
||||||
|
<version>2.8.2</version>
|
||||||
|
</plugin>
|
||||||
|
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
|
<version>3.7.1</version>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
</project>
|
@ -0,0 +1,51 @@
|
|||||||
|
package moto.tests;
|
||||||
|
|
||||||
|
import software.amazon.awssdk.http.SdkHttpClient;
|
||||||
|
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
|
||||||
|
import software.amazon.awssdk.http.apache.ApacheHttpClient;
|
||||||
|
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
|
||||||
|
import software.amazon.awssdk.regions.Region;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Client;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||||
|
import software.amazon.awssdk.utils.AttributeMap;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The module containing all dependencies required by the {@link Handler}.
|
||||||
|
*/
|
||||||
|
public class DependencyFactory {
|
||||||
|
|
||||||
|
private static final URI MOTO_URI = URI.create("http://localhost:5000");
|
||||||
|
|
||||||
|
private DependencyFactory() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return an instance of S3Client
|
||||||
|
*/
|
||||||
|
public static S3Client s3Client() {
|
||||||
|
return S3Client.builder()
|
||||||
|
.region(Region.US_EAST_1)
|
||||||
|
.httpClientBuilder(ApacheHttpClient.builder())
|
||||||
|
.endpointOverride(MOTO_URI)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DynamoDbClient dynamoClient() {
|
||||||
|
final AttributeMap attributeMap = AttributeMap.builder()
|
||||||
|
.put(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES, true)
|
||||||
|
.build();
|
||||||
|
// Not in use at the moment, but useful for testing
|
||||||
|
SdkHttpClient proxy_client = UrlConnectionHttpClient.builder()
|
||||||
|
.socketTimeout(Duration.ofMinutes(1))
|
||||||
|
.proxyConfiguration(proxy -> proxy.endpoint(URI.create("http://localhost:8080")))
|
||||||
|
.buildWithDefaults(attributeMap);
|
||||||
|
return DynamoDbClient.builder()
|
||||||
|
.region(Region.US_EAST_1)
|
||||||
|
// .httpClient(client)
|
||||||
|
.httpClientBuilder(ApacheHttpClient.builder())
|
||||||
|
.endpointOverride(MOTO_URI)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package moto.tests;
|
||||||
|
|
||||||
|
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.KeyType;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
|
||||||
|
|
||||||
|
public class DynamoLogic {
|
||||||
|
|
||||||
|
private final DynamoDbClient client;
|
||||||
|
public DynamoLogic() {
|
||||||
|
client = DependencyFactory.dynamoClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String createTable(String tableName, String key) {
|
||||||
|
CreateTableRequest request = CreateTableRequest.builder()
|
||||||
|
.attributeDefinitions(AttributeDefinition.builder()
|
||||||
|
.attributeName(key)
|
||||||
|
.attributeType(ScalarAttributeType.S)
|
||||||
|
.build())
|
||||||
|
.keySchema(KeySchemaElement.builder()
|
||||||
|
.attributeName(key)
|
||||||
|
.keyType(KeyType.HASH)
|
||||||
|
.build())
|
||||||
|
.provisionedThroughput(ProvisionedThroughput.builder()
|
||||||
|
.readCapacityUnits(new Long(10))
|
||||||
|
.writeCapacityUnits(new Long(10))
|
||||||
|
.build())
|
||||||
|
.tableName(tableName)
|
||||||
|
.build();
|
||||||
|
System.out.println("Prepared CreateTableRequest");
|
||||||
|
|
||||||
|
CreateTableResponse response = this.client.createTable(request);
|
||||||
|
System.out.println(response.tableDescription());
|
||||||
|
|
||||||
|
return response.tableDescription().tableName();
|
||||||
|
}
|
||||||
|
}
|
83
other_langs/tests_java/src/main/java/moto/tests/Handler.java
Normal file
83
other_langs/tests_java/src/main/java/moto/tests/Handler.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package moto.tests;
|
||||||
|
|
||||||
|
import software.amazon.awssdk.core.sync.RequestBody;
|
||||||
|
import software.amazon.awssdk.services.s3.S3Client;
|
||||||
|
import software.amazon.awssdk.services.s3.model.CreateBucketRequest;
|
||||||
|
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest;
|
||||||
|
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
|
||||||
|
import software.amazon.awssdk.services.s3.model.HeadBucketRequest;
|
||||||
|
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
|
||||||
|
import software.amazon.awssdk.services.s3.model.S3Exception;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example class as provided by AWS - verify that basic S3 functions work
|
||||||
|
*/
|
||||||
|
public class Handler {
|
||||||
|
private final S3Client s3Client;
|
||||||
|
|
||||||
|
public Handler() {
|
||||||
|
s3Client = DependencyFactory.s3Client();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendRequest() {
|
||||||
|
String bucket = "bucket" + System.currentTimeMillis();
|
||||||
|
String key = "key";
|
||||||
|
|
||||||
|
tutorialSetup(s3Client, bucket);
|
||||||
|
|
||||||
|
System.out.println("Uploading object...");
|
||||||
|
|
||||||
|
s3Client.putObject(PutObjectRequest.builder().bucket(bucket).key(key)
|
||||||
|
.build(),
|
||||||
|
RequestBody.fromString("Testing with the {sdk-java}"));
|
||||||
|
|
||||||
|
System.out.println("Upload complete");
|
||||||
|
System.out.printf("%n");
|
||||||
|
|
||||||
|
cleanUp(s3Client, bucket, key);
|
||||||
|
|
||||||
|
System.out.println("Closing the connection to {S3}");
|
||||||
|
s3Client.close();
|
||||||
|
System.out.println("Connection closed");
|
||||||
|
System.out.println("Exiting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tutorialSetup(S3Client s3Client, String bucketName) {
|
||||||
|
try {
|
||||||
|
s3Client.createBucket(CreateBucketRequest
|
||||||
|
.builder()
|
||||||
|
.bucket(bucketName)
|
||||||
|
.build());
|
||||||
|
System.out.println("Creating bucket: " + bucketName);
|
||||||
|
s3Client.waiter().waitUntilBucketExists(HeadBucketRequest.builder()
|
||||||
|
.bucket(bucketName)
|
||||||
|
.build());
|
||||||
|
System.out.println(bucketName + " is ready.");
|
||||||
|
System.out.printf("%n");
|
||||||
|
} catch (S3Exception e) {
|
||||||
|
System.err.println(e.awsErrorDetails().errorMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void cleanUp(S3Client s3Client, String bucketName, String keyName) {
|
||||||
|
System.out.println("Cleaning up...");
|
||||||
|
try {
|
||||||
|
System.out.println("Deleting object: " + keyName);
|
||||||
|
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(bucketName).key(keyName).build();
|
||||||
|
s3Client.deleteObject(deleteObjectRequest);
|
||||||
|
System.out.println(keyName + " has been deleted.");
|
||||||
|
System.out.println("Deleting bucket: " + bucketName);
|
||||||
|
DeleteBucketRequest deleteBucketRequest = DeleteBucketRequest.builder().bucket(bucketName).build();
|
||||||
|
s3Client.deleteBucket(deleteBucketRequest);
|
||||||
|
System.out.println(bucketName + " has been deleted.");
|
||||||
|
System.out.printf("%n");
|
||||||
|
} catch (S3Exception e) {
|
||||||
|
System.err.println(e.awsErrorDetails().errorMessage());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
System.out.println("Cleanup complete");
|
||||||
|
System.out.printf("%n");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package moto.tests;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import software.amazon.awssdk.services.dynamodb.model.ResourceInUseException;
|
||||||
|
|
||||||
|
public class DynamoTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSingleTableExists() {
|
||||||
|
DynamoLogic logic = new DynamoLogic();
|
||||||
|
String table_name = logic.createTable("table", "key");
|
||||||
|
|
||||||
|
assertEquals("table", table_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTableCannotBeCreatedTwice() {
|
||||||
|
DynamoLogic logic = new DynamoLogic();
|
||||||
|
// Going once...
|
||||||
|
logic.createTable("table2", "key");
|
||||||
|
try {
|
||||||
|
// Going twice should throw a specific exception
|
||||||
|
logic.createTable("table2", "key");
|
||||||
|
} catch (ResourceInUseException riue) {
|
||||||
|
assertTrue(riue.getMessage().startsWith("Table already exists: table2"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
other_langs/tests_java/src/test/java/moto/tests/S3Test.java
Normal file
16
other_langs/tests_java/src/test/java/moto/tests/S3Test.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package moto.tests;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for simple App.
|
||||||
|
*/
|
||||||
|
public class S3Test
|
||||||
|
{
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void connectToS3() {
|
||||||
|
Handler handler = new Handler();
|
||||||
|
handler.sendRequest();
|
||||||
|
}
|
||||||
|
}
|
@ -24,9 +24,7 @@ def test_table_list():
|
|||||||
res = test_client.post(
|
res = test_client.post(
|
||||||
"/", headers=headers, data=json.dumps({"TableName": "test-table2"})
|
"/", headers=headers, data=json.dumps({"TableName": "test-table2"})
|
||||||
)
|
)
|
||||||
res.headers.should.have.key("X-Amzn-ErrorType").equals(
|
res.headers.should.have.key("X-Amzn-ErrorType").equals("ResourceNotFoundException")
|
||||||
"com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
|
|
||||||
)
|
|
||||||
body = json.loads(res.data.decode("utf-8"))
|
body = json.loads(res.data.decode("utf-8"))
|
||||||
body.should.equal(
|
body.should.equal(
|
||||||
{
|
{
|
||||||
|
Loading…
x
Reference in New Issue
Block a user