From 1040830a644d9789efc247232b2fb86bf7934f7b Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 3 Mar 2025 17:43:09 +0000 Subject: [PATCH] De-mock DynamoDB tests. --- .github/workflows/budibase_ci.yml | 3 + .../src/integrations/tests/dynamodb.spec.ts | 170 ++++++------------ 2 files changed, 61 insertions(+), 112 deletions(-) diff --git a/.github/workflows/budibase_ci.yml b/.github/workflows/budibase_ci.yml index 2e7851b338..c8bdfe9655 100644 --- a/.github/workflows/budibase_ci.yml +++ b/.github/workflows/budibase_ci.yml @@ -165,6 +165,7 @@ jobs: oracle, sqs, elasticsearch, + dynamodb, none, ] steps: @@ -205,6 +206,8 @@ jobs: docker pull postgres:9.5.25 elif [ "${{ matrix.datasource }}" == "elasticsearch" ]; then docker pull elasticsearch@${{ steps.dotenv.outputs.ELASTICSEARCH_SHA }} + elif [ "${{ matrix.datasource }}" == "dynamodb" ]; then + docker pull amazon/dynamodb-local@${{ steps.dotenv.outputs.DYNAMODB_SHA }} fi docker pull minio/minio & docker pull redis & diff --git a/packages/server/src/integrations/tests/dynamodb.spec.ts b/packages/server/src/integrations/tests/dynamodb.spec.ts index 49863c9912..f7aafc6932 100644 --- a/packages/server/src/integrations/tests/dynamodb.spec.ts +++ b/packages/server/src/integrations/tests/dynamodb.spec.ts @@ -1,149 +1,95 @@ import { Datasource } from "@budibase/types" import { DynamoDBConfig, DynamoDBIntegration } from "../dynamodb" import { DatabaseName, datasourceDescribe } from "./utils" +import { CreateTableCommandInput, DynamoDB } from "@aws-sdk/client-dynamodb" const describes = datasourceDescribe({ only: [DatabaseName.DYNAMODB] }) +async function createTable(client: DynamoDB, req: CreateTableCommandInput) { + try { + await client.deleteTable({ TableName: req.TableName }) + } catch (e: any) { + if (e.name !== "ResourceNotFoundException") { + throw e + } + } + + return await client.createTable(req) +} + if (describes.length > 0) { describe.each(describes)("DynamoDB Integration", ({ dsProvider }) => { - let tableName = "Users" + let table = "Users" let rawDatasource: Datasource let dynamodb: DynamoDBIntegration + function item(json: Record) { + return { table, json: { Item: json } } + } + + function key(json: Record) { + return { table, json: { Key: json } } + } + beforeEach(async () => { const ds = await dsProvider() rawDatasource = ds.rawDatasource! dynamodb = new DynamoDBIntegration( rawDatasource.config! as DynamoDBConfig ) - }) - it.only("calls the create method with the correct params", async () => { - await dynamodb.create({ - table: tableName, - json: { - Name: "John", - }, + const client = new DynamoDB(rawDatasource.config as DynamoDBConfig) + await createTable(client, { + TableName: table, + KeySchema: [{ AttributeName: "Id", KeyType: "HASH" }], + AttributeDefinitions: [{ AttributeName: "Id", AttributeType: "N" }], + ProvisionedThroughput: { ReadCapacityUnits: 1, WriteCapacityUnits: 1 }, }) }) - it("calls the read method with the correct params", async () => { - const indexName = "Test" + it("can create and read a record", async () => { + await dynamodb.create(item({ Id: 1, Name: "John" })) - const response = await dynamodb.read({ - table: tableName, - index: indexName, - json: {}, - }) - expect(config.integration.client.query).toHaveBeenCalledWith({ - TableName: tableName, - IndexName: indexName, - }) - expect(response).toEqual([]) + const resp = await dynamodb.get(key({ Id: 1 })) + expect(resp.Item).toEqual({ Id: 1, Name: "John" }) }) - it("calls the scan method with the correct params", async () => { - const indexName = "Test" + it("can scan", async () => { + await dynamodb.create(item({ Id: 1, Name: "John" })) + await dynamodb.create(item({ Id: 2, Name: "Jane" })) + await dynamodb.create(item({ Id: 3, Name: "Jack" })) - const response = await dynamodb.scan({ - table: tableName, - index: indexName, - json: {}, - }) - expect(config.integration.client.scan).toHaveBeenCalledWith({ - TableName: tableName, - IndexName: indexName, - }) - expect(response).toEqual([]) + const resp = await dynamodb.scan({ table, json: {}, index: null }) + expect(resp).toEqual( + expect.arrayContaining([ + { Id: 1, Name: "John" }, + { Id: 2, Name: "Jane" }, + { Id: 3, Name: "Jack" }, + ]) + ) }) - it("calls the get method with the correct params", async () => { - await dynamodb.get({ - table: tableName, - json: { - Id: 123, - }, - }) - - expect(config.integration.client.get).toHaveBeenCalledWith({ - TableName: tableName, - Id: 123, - }) - }) - - it("calls the update method with the correct params", async () => { + it("can update", async () => { + await dynamodb.create(item({ Id: 1, Foo: "John" })) await dynamodb.update({ - table: tableName, + table, json: { - Name: "John", + Key: { Id: 1 }, + UpdateExpression: "SET Foo = :foo", + ExpressionAttributeValues: { ":foo": "Jane" }, }, }) - expect(config.integration.client.update).toHaveBeenCalledWith({ - TableName: tableName, - Name: "John", - }) + + const updatedRecord = await dynamodb.get(key({ Id: 1 })) + expect(updatedRecord.Item).toEqual({ Id: 1, Foo: "Jane" }) }) - it("calls the delete method with the correct params", async () => { - await dynamodb.delete({ - table: tableName, - json: { - Name: "John", - }, - }) - expect(config.integration.client.delete).toHaveBeenCalledWith({ - TableName: tableName, - Name: "John", - }) - }) + it("can delete", async () => { + await dynamodb.create(item({ Id: 1, Name: "John" })) + await dynamodb.delete(key({ Id: 1 })) - it("configures the dynamoDB constructor based on an empty endpoint parameter", async () => { - const config = { - region: "us-east-1", - accessKeyId: "test", - secretAccessKey: "test", - } - - const integration: any = new DynamoDBIntegration.integration(config) - - expect(integration.config).toEqual({ - currentClockSkew: true, - ...config, - }) - }) - - it("configures the dynamoDB constructor based on a localhost endpoint parameter", async () => { - const config = { - region: "us-east-1", - accessKeyId: "test", - secretAccessKey: "test", - endpoint: "localhost:8080", - } - - const integration: any = new DynamoDBIntegration.integration(config) - - expect(integration.config).toEqual({ - region: "us-east-1", - currentClockSkew: true, - endpoint: "localhost:8080", - }) - }) - - it("configures the dynamoDB constructor based on a remote endpoint parameter", async () => { - const config = { - region: "us-east-1", - accessKeyId: "test", - secretAccessKey: "test", - endpoint: "dynamodb.aws.foo.net", - } - - const integration = new DynamoDBIntegration.integration(config) - - // @ts-ignore - expect(integration.config).toEqual({ - currentClockSkew: true, - ...config, - }) + const deletedRecord = await dynamodb.get(key({ Id: 1 })) + expect(deletedRecord.Item).toBeUndefined() }) }) }