From 2dec181929d4fce612725c96e7cea45fe66d1678 Mon Sep 17 00:00:00 2001 From: Bregwin Jogi Date: Mon, 22 Jul 2024 20:34:12 -0400 Subject: [PATCH] Update docker-compose.yml to include AWS configuration for S3 and DynamoDB, updated post and get routes to fix location header --- docker-compose.yml | 26 ++++++++++++ scripts/local-aws-setup.sh | 49 ++++++++++++++++++++++ src/routes/api/get.js | 2 +- src/routes/api/post.js | 2 +- tests/integration/404.hurl | 4 +- tests/integration/post-fragments-json.hurl | 6 +-- tests/integration/post-fragments.hurl | 2 +- tests/unit/post.test.js | 2 +- 8 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 scripts/local-aws-setup.sh diff --git a/docker-compose.yml b/docker-compose.yml index b03413a..9593629 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,17 @@ services: - HTPASSWD_FILE=tests/.htpasswd # Use the LOG_LEVEL set in the host environment, or default to info - LOG_LEVEL=${LOG_LEVEL:-info} + - AWS_REGION=us-east-1 + # Use the LocalStack endpoint vs. AWS for S3 AWS SDK clients. + # NOTE: we use Docker's internal network to the localstack container + - AWS_S3_ENDPOINT_URL=http://localstack:4566 + # Use the DynamoDB local endpoint vs. AWS for DynamoDB AWS SDK clients. + - AWS_DYNAMODB_ENDPOINT_URL=http://dynamodb-local:8000 + # This S3 bucket and DynamoDB table need to get created first, see + # local-aws-setup.sh. We'll default to 'fragments' as the name, unless + # something else is defined in the env. + - AWS_S3_BUCKET_NAME=${AWS_S3_BUCKET_NAME:-fragments} + - AWS_DYNAMODB_TABLE_NAME=${AWS_DYNAMODB_TABLE_NAME:-fragments} # Ports to publish ports: - '8080:8080' @@ -29,3 +40,18 @@ services: # Run the database in memory, see: # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBLocal.UsageNotes.html command: ['-jar', 'DynamoDBLocal.jar', '-inMemory'] + + # LocalStack for S3, see https://docs.localstack.cloud/get-started/#docker-compose + # Interact via awscli-local, see https://docs.localstack.cloud/integrations/aws-cli/#installation + localstack: + # https://hub.docker.com/r/localstack/localstack + image: localstack/localstack + ports: + - '4566:4566' + environment: + # See https://docs.localstack.cloud/localstack/configuration/ and + # https://hub.docker.com/r/localstack/localstack for config details. + # We only want to run S3 + - SERVICES=s3 + # We're always working in us-east-1 + - DEFAULT_REGION=us-east-1 diff --git a/scripts/local-aws-setup.sh b/scripts/local-aws-setup.sh new file mode 100644 index 0000000..de66162 --- /dev/null +++ b/scripts/local-aws-setup.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +# Setup steps for working with LocalStack and DynamoDB local instead of AWS. +# Assumes aws cli is installed and LocalStack and DynamoDB local are running. + +# Setup AWS environment variables +echo "Setting AWS environment variables for LocalStack" + +echo "AWS_ACCESS_KEY_ID=test" +export AWS_ACCESS_KEY_ID=test + +echo "AWS_SECRET_ACCESS_KEY=test" +export AWS_SECRET_ACCESS_KEY=test + +echo "AWS_SESSION_TOKEN=test" +export AWS_SESSION_TOKEN=test + +export AWS_DEFAULT_REGION=us-east-1 +echo "AWS_DEFAULT_REGION=us-east-1" + +# Wait for LocalStack to be ready, by inspecting the response from healthcheck +echo 'Waiting for LocalStack S3...' +until (curl --silent http://localhost:4566/_localstack/health | grep "\"s3\": \"\(running\|available\)\"" > /dev/null); do + sleep 5 +done +echo 'LocalStack S3 Ready' + +# Create our S3 bucket with LocalStack +echo "Creating LocalStack S3 bucket: fragments" +aws --endpoint-url=http://localhost:4566 s3api create-bucket --bucket fragments + +# Setup DynamoDB Table with dynamodb-local, see: +# https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/getting-started-step-1.html +echo "Creating DynamoDB-Local DynamoDB table: fragments" +aws --endpoint-url=http://localhost:8000 \ +dynamodb create-table \ + --table-name fragments \ + --attribute-definitions \ + AttributeName=ownerId,AttributeType=S \ + AttributeName=id,AttributeType=S \ + --key-schema \ + AttributeName=ownerId,KeyType=HASH \ + AttributeName=id,KeyType=RANGE \ + --provisioned-throughput \ + ReadCapacityUnits=10,WriteCapacityUnits=5 + +# Wait until the Fragments table exists in dynamodb-local, so we can use it, see: +# https://awscli.amazonaws.com/v2/documentation/api/latest/reference/dynamodb/wait/table-exists.html +aws --endpoint-url=http://localhost:8000 dynamodb wait table-exists --table-name fragments diff --git a/src/routes/api/get.js b/src/routes/api/get.js index 80f7130..ad6dcb1 100644 --- a/src/routes/api/get.js +++ b/src/routes/api/get.js @@ -82,7 +82,7 @@ module.exports = (req, res, next) => { } else { // Return the data with original content type it had - res.status(200).send(data.toString()); + res.status(200).header('Content-Type', fragment.mimeType).send(data.toString()); } }); }) diff --git a/src/routes/api/post.js b/src/routes/api/post.js index 3f1e2c0..04a07fb 100644 --- a/src/routes/api/post.js +++ b/src/routes/api/post.js @@ -27,7 +27,7 @@ module.exports = (req, res) => { fragment.save().then(() => { //Setting Location Header - const hostName = process.env.API_URL || req.headers.host; + const hostName = req.headers.host; const locationURL = new URL("/v1/fragments/" + fragment.id, `http://${hostName}`); res.location(locationURL.toString()); diff --git a/tests/integration/404.hurl b/tests/integration/404.hurl index 937c26c..049c879 100644 --- a/tests/integration/404.hurl +++ b/tests/integration/404.hurl @@ -1,5 +1,5 @@ -Check if +#Check if invalid route returns 404 response GET http://localhost:8080/no-such-route #We should get a 404 response -HTTP/1.0 404 +HTTP/1.1 404 diff --git a/tests/integration/post-fragments-json.hurl b/tests/integration/post-fragments-json.hurl index 8293aef..e7cd5df 100644 --- a/tests/integration/post-fragments-json.hurl +++ b/tests/integration/post-fragments-json.hurl @@ -6,7 +6,7 @@ Content-Type: application/json [BasicAuth] user1@email.com:password1 -`{"key": "value"}` +`{"hi": "something"}` # We expect to get back an HTTP 201 HTTP/1.1 201 @@ -24,6 +24,6 @@ GET {{url}} user1@email.com:password1 HTTP/1.1 200 -Content-Type: application/json +Content-Type: application/json; charset=utf-8 [Asserts] -body == '{"key": "value"}' +body == "{\"hi\": \"something\"}" diff --git a/tests/integration/post-fragments.hurl b/tests/integration/post-fragments.hurl index 936450e..e99ade4 100644 --- a/tests/integration/post-fragments.hurl +++ b/tests/integration/post-fragments.hurl @@ -37,7 +37,7 @@ GET {{url}} user1@email.com:password1 HTTP/1.1 200 -Content-Type: text/plain +Content-Type: text/plain; charset=utf-8 Content-Length: 19 [Asserts] body == "This is a fragment!" diff --git a/tests/unit/post.test.js b/tests/unit/post.test.js index 5265b0e..103b5f6 100644 --- a/tests/unit/post.test.js +++ b/tests/unit/post.test.js @@ -13,7 +13,7 @@ describe('POST /v1/fragments', () => { request(app).post('/v1/fragments').auth('invalid@email.com', 'incorrect_password').expect(401)); // Using a valid username/password pair should save a new fragment and return a successful response - test('authenticated users get a fragments array', async () => { + test('authenticated users get successful response when creating valid fragment', async () => { const res = await request(app) .post('/v1/fragments') .auth('user1@email.com', 'password1')