-
Notifications
You must be signed in to change notification settings - Fork 2
[IS-9/feat]: as a library user i want to create the environment in order to allocate resources for the connections #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
865017e
9550987
6e67790
332fb3d
a861fbd
bd63777
6dd56ae
e20dab7
b35b006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ | |
"vitest": "^3.1.3" | ||
}, | ||
"dependencies": { | ||
"assertion-error": "^2.0.1", | ||
"rhea": "^3.0.4" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { ConnectionEvents, Connection as RheaConnection } from "rhea" | ||
|
||
export interface Connection { | ||
close(): Promise<boolean> | ||
isOpen(): boolean | ||
} | ||
|
||
export class AmqpConnection implements Connection { | ||
private readonly rheaConnection: RheaConnection | ||
|
||
constructor(connection: RheaConnection) { | ||
this.rheaConnection = connection | ||
} | ||
|
||
async close(): Promise<boolean> { | ||
return new Promise((res, rej) => { | ||
this.rheaConnection.once(ConnectionEvents.connectionClose, () => { | ||
return res(true) | ||
}) | ||
this.rheaConnection.once(ConnectionEvents.connectionError, (context) => { | ||
return rej(new Error("Connection error: " + context.connection.error)) | ||
}) | ||
|
||
this.rheaConnection.close() | ||
}) | ||
} | ||
|
||
public isOpen(): boolean { | ||
return this.rheaConnection.is_open() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { ConnectionEvents, Container, create_container } from "rhea" | ||
import { AmqpConnection, Connection } from "./connection.js" | ||
import { Connection as RheaConnection } from "rhea" | ||
|
||
export interface Environment { | ||
createConnection(): Promise<Connection> | ||
close(): Promise<void> | ||
} | ||
|
||
export type EnvironmentParams = { | ||
host: string | ||
port: number | ||
username: string | ||
password: string | ||
} | ||
|
||
export class AmqpEnvironment implements Environment { | ||
private readonly host: string | ||
private readonly port: number | ||
private readonly username: string | ||
private readonly password: string | ||
private readonly container: Container | ||
private connections: Connection[] = [] | ||
|
||
constructor({ host, port, username, password }: EnvironmentParams) { | ||
this.host = host | ||
this.port = port | ||
this.username = username | ||
this.password = password | ||
this.container = create_container() | ||
} | ||
|
||
async createConnection(): Promise<Connection> { | ||
const rheaConnection = await this.openConnection() | ||
const connection = new AmqpConnection(rheaConnection) | ||
this.connections.push(connection) | ||
|
||
return connection | ||
} | ||
|
||
private async openConnection(): Promise<RheaConnection> { | ||
return new Promise((res, rej) => { | ||
this.container.once(ConnectionEvents.connectionOpen, (context) => { | ||
return res(context.connection) | ||
}) | ||
this.container.once(ConnectionEvents.error, (context) => { | ||
return rej(context.error ?? new Error("Connection error occurred")) | ||
}) | ||
|
||
this.container.connect({ host: this.host, port: this.port, username: this.username, password: this.password }) | ||
}) | ||
} | ||
|
||
async close(): Promise<void> { | ||
await this.closeConnections() | ||
this.connections = [] | ||
} | ||
|
||
private async closeConnections(): Promise<void> { | ||
await Promise.allSettled( | ||
this.connections.map(async (c) => { | ||
if (c.isOpen()) await c.close() | ||
}) | ||
) | ||
} | ||
} | ||
|
||
export function createEnvironment(params: EnvironmentParams): Environment { | ||
return new AmqpEnvironment(params) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { afterEach, beforeEach, describe, test } from "vitest" | ||
import { use, expect } from "chai" | ||
import chaiAsPromised from "chai-as-promised" | ||
import { createEnvironment, Environment } from "../../src/environment.js" | ||
import { host, port, username, password, numberOfConnections, eventually } from "../support/util.js" | ||
|
||
use(chaiAsPromised) | ||
|
||
describe("Environment", () => { | ||
let environment: Environment | ||
|
||
beforeEach(async () => { | ||
environment = createEnvironment({ | ||
host, | ||
port, | ||
username, | ||
password, | ||
}) | ||
}) | ||
|
||
afterEach(async () => { | ||
await environment.close() | ||
}) | ||
|
||
test("create a connection through the environment", async () => { | ||
await environment.createConnection() | ||
|
||
await eventually(async () => { | ||
expect(await numberOfConnections()).to.eql(1) | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -6,7 +6,7 @@ import chaiAsPromised from "chai-as-promised" | |||||
|
||||||
use(chaiAsPromised) | ||||||
|
||||||
describe("Management", () => { | ||||||
describe.skip("Management", () => { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
Copilot uses AI. Check for mistakes. Positive FeedbackNegative Feedback |
||||||
let management: Management | ||||||
|
||||||
beforeEach(() => { | ||||||
|
@@ -17,7 +17,7 @@ describe("Management", () => { | |||||
management.close() | ||||||
}) | ||||||
|
||||||
test.skip("create a queue through the management", async () => { | ||||||
test("create a queue through the management", async () => { | ||||||
const queue = management.queue("test-coda").exclusive(true).autoDelete(true).declare() | ||||||
|
||||||
expect(await existsQueue(queue.name)).to.eql(true) | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import { afterEach, beforeEach, describe, test } from "vitest" | ||
import { use, expect } from "chai" | ||
import chaiAsPromised from "chai-as-promised" | ||
import { host, port, username, password, numberOfConnections, eventually } from "../../support/util.js" | ||
import { | ||
Connection, | ||
ConnectionEvents, | ||
ConnectionOptions, | ||
Container, | ||
create_container, | ||
Receiver, | ||
ReceiverEvents, | ||
ReceiverOptions, | ||
Sender, | ||
SenderEvents, | ||
SenderOptions, | ||
} from "rhea" | ||
|
||
use(chaiAsPromised) | ||
|
||
describe("Rhea tests", () => { | ||
let container: Container | ||
let connection: Connection | ||
|
||
beforeEach(async () => { | ||
container = create_container() | ||
}) | ||
|
||
afterEach(async () => { | ||
await close(connection) | ||
}) | ||
|
||
test("create a connection", async () => { | ||
connection = await open(container, { | ||
host, | ||
port, | ||
username, | ||
password, | ||
}) | ||
|
||
await eventually(async () => { | ||
expect(await numberOfConnections()).to.eql(1) | ||
}) | ||
}) | ||
|
||
test("connect to the management", async () => { | ||
connection = await open(container, { | ||
host, | ||
port, | ||
username, | ||
password, | ||
}) | ||
|
||
await eventually(async () => { | ||
await openSender(connection) | ||
await openReceiver(connection) | ||
}, 4000) | ||
}) | ||
}) | ||
|
||
async function open(container: Container, params: ConnectionOptions): Promise<Connection> { | ||
return new Promise((res, rej) => { | ||
container.once(ConnectionEvents.connectionOpen, (context) => { | ||
return res(context.connection) | ||
}) | ||
container.once(ConnectionEvents.error, (context) => { | ||
return rej(context.connection.error) | ||
}) | ||
container.connect(params) | ||
}) | ||
} | ||
|
||
async function close(connection: Connection): Promise<void> { | ||
return new Promise((res, rej) => { | ||
connection.once(ConnectionEvents.connectionClose, () => { | ||
res() | ||
}) | ||
connection.once(ConnectionEvents.connectionError, (context) => { | ||
rej(new Error("Connection error: " + context.connection.error)) | ||
}) | ||
connection.close() | ||
}) | ||
} | ||
|
||
const MANAGEMENT_NODE_CONFIGURATION: SenderOptions | ReceiverOptions = { | ||
snd_settle_mode: 1, | ||
rcv_settle_mode: 0, | ||
name: "management-link-pair", | ||
target: { address: "/management", expiry_policy: "LINK_DETACH", timeout: 0, dynamic: false }, | ||
source: { address: "/management", expiry_policy: "LINK_DETACH", timeout: 0, dynamic: false, durable: 0 }, | ||
properties: { paired: true }, | ||
} | ||
|
||
async function openReceiver(connection: Connection) { | ||
return openLink( | ||
connection, | ||
ReceiverEvents.receiverOpen, | ||
ReceiverEvents.receiverError, | ||
connection.open_receiver.bind(connection), | ||
MANAGEMENT_NODE_CONFIGURATION | ||
) | ||
} | ||
|
||
async function openSender(connection: Connection) { | ||
return openLink( | ||
connection, | ||
SenderEvents.senderOpen, | ||
SenderEvents.senderError, | ||
connection.open_sender.bind(connection), | ||
MANAGEMENT_NODE_CONFIGURATION | ||
) | ||
} | ||
|
||
type LinkOpenEvents = SenderEvents.senderOpen | ReceiverEvents.receiverOpen | ||
type LinkErrorEvents = SenderEvents.senderError | ReceiverEvents.receiverError | ||
type OpenLinkMethods = | ||
| ((options?: SenderOptions | string) => Sender) | ||
| ((options?: ReceiverOptions | string) => Receiver) | ||
|
||
async function openLink( | ||
connection: Connection, | ||
successEvent: LinkOpenEvents, | ||
errorEvent: LinkErrorEvents, | ||
openMethod: OpenLinkMethods, | ||
config?: SenderOptions | ReceiverOptions | string | ||
): Promise<Sender | Receiver> { | ||
return new Promise((res, rej) => { | ||
connection.once(successEvent, (context) => { | ||
return res(context.receiver || context.sender) | ||
}) | ||
connection.once(errorEvent, (context) => { | ||
return rej(context.connection.error) | ||
}) | ||
openMethod(config) | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick]
close
clears the tracked connections but does not remove any event listeners fromthis.container
; consider disposing or removing listeners to prevent memory leaks in long-lived processes.Copilot uses AI. Check for mistakes.