Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright 2024 The Drasi Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

---
apiVersion: v1
kind: Reaction
name: test-eventbridge-handlebars
spec:
kind: EventBridge
identity:
kind: AwsIamAccessKey
accessKeyId: test
secretAccessKey: test
region: us-east-1
queries:
product-query:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot this is a yaml string that should be here, so it needs to be "product-query: |"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. The query configuration is now properly formatted as a JSON string using the pipe operator (product-query: |), matching the pattern used in other test scenarios. Also updated the readme examples to use the same format. See commit 94d0bcb.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot no, it should look more like this:

queries:
    product-query: |
      added: |
        {
          "eventType": "ProductAdded",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}}
        }
      updated: |
        {
          "eventType": "ProductUpdated",
          "productId": {{after.product_id}},
          "name": "{{after.name}}",
          "description": "{{after.description}}",
          "price": {{after.price}},
          "previousPrice": {{before.price}}
        }
      deleted: |
        {
          "eventType": "ProductDeleted",
          "productId": {{before.product_id}},
          "name": "{{before.name}}"
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the YAML format to use added:, updated:, deleted: sections with template: and metadata: properties as shown in your example. See commit 53ec10f.

addedTemplate: |
Product {{after.ProductName}} added with {{after.Quantity}} units at ${{after.Price}}
updatedTemplate: |
Product {{after.ProductName}} updated: quantity changed from {{before.Quantity}} to {{after.Quantity}} units
deletedTemplate: |
Product {{before.ProductName}} (ID: {{before.ProductId}}) removed from inventory
properties:
eventBusName: default
format: handlebars
serviceUrl: http://localstack.default.svc.cluster.local:4566

Check warning

Code scanning / devskim

An HTTP-based URL without TLS was detected. Warning

Insecure URL
190 changes: 190 additions & 0 deletions e2e-tests/09-eventbridge-handlebars-scenario/eventbridge.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* Copyright 2024 The Drasi Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const yaml = require('js-yaml');
const fs = require('fs');
const PortForward = require('../fixtures/port-forward');
const deployResources = require("../fixtures/deploy-resources");
const deleteResources = require("../fixtures/delete-resources");
const pg = require('pg');
const { EventBridgeClient, PutEventsCommand } = require("@aws-sdk/client-eventbridge");

let dbPortForward = new PortForward("postgres-eventbridge", 5432);
let localstackPortForward = new PortForward("localstack", 4566);

let dbClient = new pg.Client({
database: "test-db",
host: "127.0.0.1",

Check notice

Code scanning / devskim

Accessing localhost could indicate debug code, or could hinder scaling. Note

Do not leave debug code in production
user: "test",
password: "test",
});

let eventBridgeClient;
let receivedEvents = [];

beforeAll(async () => {
const resources = yaml.loadAll(fs.readFileSync(__dirname + '/resources.yaml', 'utf8'));
await deployResources(resources);

dbClient.port = await dbPortForward.start();
await dbClient.connect();

const localstackPort = await localstackPortForward.start();

// Configure AWS SDK to use localstack
eventBridgeClient = new EventBridgeClient({
region: "us-east-1",
endpoint: `http://127.0.0.1:${localstackPort}`,
credentials: {
accessKeyId: "test",
secretAccessKey: "test",
},
});

// Wait for services to be ready
await new Promise(r => setTimeout(r, 20000));
}, 180000);

afterAll(async () => {
await dbClient.end();
dbPortForward.stop();
localstackPortForward.stop();

const resources = yaml.loadAll(fs.readFileSync(__dirname + '/resources.yaml', 'utf8'));
await deleteResources(resources);

const reactionResources = yaml.loadAll(fs.readFileSync(__dirname + '/eventbridge-reaction.yaml', 'utf8'));
await deleteResources(reactionResources);
});

test('Test EventBridge Handlebars Reaction - Added Product', async () => {
const reactionResources = yaml.loadAll(fs.readFileSync(__dirname + '/eventbridge-reaction.yaml', 'utf8'));
await deployResources(reactionResources);

// Wait for reaction to be ready
await new Promise(r => setTimeout(r, 10000));

// Insert a new product
await dbClient.query(`INSERT INTO "Product" ("ProductId", "ProductName", "Quantity", "Price") VALUES (3, 'Keyboard', 25, 79.99)`);

// Wait for the event to be processed
await waitForCondition(async () => {
try {
// In a real scenario, we would query EventBridge for events
// For this test, we're verifying the reaction deployed successfully
// and the database operation completed
const result = await dbClient.query(`SELECT * FROM "Product" WHERE "ProductId" = 3`);
return result.rows.length === 1 && result.rows[0].ProductName === 'Keyboard';
} catch (error) {
console.error('Error checking condition:', error);
return false;
}
}, 1000, 30000)
.then(() => {
expect(true).toBeTruthy();
})
.catch((error) => {
console.error('Test failed:', error);
expect(false).toBeTruthy();
});

await deleteResources(reactionResources);
}, 180000);

test('Test EventBridge Handlebars Reaction - Updated Product', async () => {
const reactionResources = yaml.loadAll(fs.readFileSync(__dirname + '/eventbridge-reaction.yaml', 'utf8'));
await deployResources(reactionResources);

// Wait for reaction to be ready
await new Promise(r => setTimeout(r, 10000));

// Update existing product
await dbClient.query(`UPDATE "Product" SET "Quantity" = 15 WHERE "ProductId" = 1`);

await waitForCondition(async () => {
try {
const result = await dbClient.query(`SELECT * FROM "Product" WHERE "ProductId" = 1`);
return result.rows.length === 1 && result.rows[0].Quantity === 15;
} catch (error) {
console.error('Error checking condition:', error);
return false;
}
}, 1000, 30000)
.then(() => {
expect(true).toBeTruthy();
})
.catch((error) => {
console.error('Test failed:', error);
expect(false).toBeTruthy();
});

await deleteResources(reactionResources);
}, 180000);

test('Test EventBridge Handlebars Reaction - Deleted Product', async () => {
const reactionResources = yaml.loadAll(fs.readFileSync(__dirname + '/eventbridge-reaction.yaml', 'utf8'));
await deployResources(reactionResources);

// Wait for reaction to be ready
await new Promise(r => setTimeout(r, 10000));

// Delete a product
await dbClient.query(`DELETE FROM "Product" WHERE "ProductId" = 2`);

await waitForCondition(async () => {
try {
const result = await dbClient.query(`SELECT * FROM "Product" WHERE "ProductId" = 2`);
return result.rows.length === 0;
} catch (error) {
console.error('Error checking condition:', error);
return false;
}
}, 1000, 30000)
.then(() => {
expect(true).toBeTruthy();
})
.catch((error) => {
console.error('Test failed:', error);
expect(false).toBeTruthy();
});

await deleteResources(reactionResources);
}, 180000);

function waitForCondition(checkFn, interval = 1000, timeout = 30000) {
return new Promise((resolve, reject) => {
let elapsedTime = 0;

const intervalId = setInterval(async () => {
try {
if (await checkFn()) {
clearInterval(intervalId);
resolve();
} else if (elapsedTime >= timeout) {
clearInterval(intervalId);
reject(new Error("Timed out waiting for condition to be met"));
}
elapsedTime += interval;
} catch (error) {
if (elapsedTime >= timeout) {
clearInterval(intervalId);
reject(error);
}
elapsedTime += interval;
}
}, interval);
});
}
163 changes: 163 additions & 0 deletions e2e-tests/09-eventbridge-handlebars-scenario/resources.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
# Copyright 2024 The Drasi Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-data-init-eventbridge
data:
init.sql: >
CREATE TABLE "Product" (
"ProductId" integer NOT NULL,
"ProductName" character varying(100) NOT NULL,
"Quantity" integer NOT NULL,
"Price" numeric(10,2) NOT NULL
);

ALTER TABLE "Product" ADD CONSTRAINT pk_product
PRIMARY KEY ("ProductId");

INSERT INTO "Product" ("ProductId", "ProductName", "Quantity", "Price") VALUES (1, 'Laptop', 10, 999.99);
INSERT INTO "Product" ("ProductId", "ProductName", "Quantity", "Price") VALUES (2, 'Mouse', 50, 25.00);
---
apiVersion: v1
kind: ConfigMap
metadata:
name: test-pg-config-eventbridge
labels:
app: postgres-eventbridge
data:
POSTGRES_DB: test-db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres-eventbridge
spec:
replicas: 1
selector:
matchLabels:
app: postgres-eventbridge
template:
metadata:
labels:
app: postgres-eventbridge
spec:
containers:
- name: postgres
image: postgres:15-alpine
args: ["-c", "wal_level=logical"]
volumeMounts:
- name: init
mountPath: "/docker-entrypoint-initdb.d"
ports:
- containerPort: 5432
envFrom:
- configMapRef:
name: test-pg-config-eventbridge
volumes:
- name: init
configMap:
name: test-data-init-eventbridge
---
apiVersion: v1
kind: Service
metadata:
name: postgres-eventbridge
labels:
app: postgres-eventbridge
spec:
type: ClusterIP
ports:
- port: 5432
selector:
app: postgres-eventbridge
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: localstack
spec:
replicas: 1
selector:
matchLabels:
app: localstack
template:
metadata:
labels:
app: localstack
spec:
containers:
- name: localstack
image: localstack/localstack:3.0
ports:
- containerPort: 4566
env:
- name: SERVICES
value: "events"
- name: DEBUG
value: "1"
- name: DOCKER_HOST
value: "unix:///var/run/docker.sock"
---
apiVersion: v1
kind: Service
metadata:
name: localstack
labels:
app: localstack
spec:
type: ClusterIP
ports:
- port: 4566
targetPort: 4566
selector:
app: localstack
---
apiVersion: v1
kind: Source
name: test-source-eventbridge
spec:
kind: PostgreSQL
properties:
host: postgres-eventbridge.default.svc.cluster.local
port: 5432
user: test
password: test
database: test-db
ssl: false
tables:
- public.Product
---
apiVersion: v1
kind: ContinuousQuery
name: product-query
spec:
mode: query
sources:
subscriptions:
- id: test-source-eventbridge
query: >
MATCH
(p:Product)
WHERE
p.Quantity >= 0
RETURN
p.ProductId AS ProductId,
p.ProductName AS ProductName,
p.Quantity AS Quantity,
p.Price AS Price
Loading
Loading