Skip to content

Commit 1a1fbdd

Browse files
committed
feat(inbox): Add local test function - working locally!
1 parent 493b72b commit 1a1fbdd

File tree

5 files changed

+85
-65
lines changed

5 files changed

+85
-65
lines changed

src/construct.handler.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import * as awsLambda from 'aws-lambda';
22

3-
43
import { getSecrets, getRedisConnection, getQueueDepth, publishMetrics } from './methods';
54

6-
75
export interface Result {
86
region: string;
97
vpcId: string;
108
count: number;
119
}
1210

13-
1411
export const handler = async () => {
1512
try {
1613
const host = process.env.REDIS_ADDR;
@@ -27,12 +24,12 @@ export const handler = async () => {
2724
throw new Error('REDIS_PORT environment variable does not parseInt');
2825
}
2926

30-
// parse queues
31-
const queuesBlob = process.env.QUEUES;
27+
// parse queue names
28+
const queuesBlob = process.env.QUEUE_NAMES;
3229
if (!queuesBlob) {
33-
throw new Error('QUEUES environment variable not set');
30+
throw new Error('QUEUE_NAMES environment variable not set');
3431
}
35-
const queues: string[] = JSON.parse(queuesBlob);
32+
const queueNames: string[] = JSON.parse(queuesBlob);
3633

3734
// get username/password
3835
const redisSecretArn = process.env.REDIS_SECRET_ARN;
@@ -50,7 +47,11 @@ export const handler = async () => {
5047
throw new Error('REDIS_SECRET_USERNAME_PATH environment variable not set');
5148
}
5249

53-
const { username, password } = await getSecrets({redisSecretArn, redisSecretPasswordPath, redisSecretUsernamePath });
50+
const { username, password } = await getSecrets({
51+
redisSecretArn,
52+
redisSecretPasswordPath,
53+
redisSecretUsernamePath,
54+
});
5455

5556
// connect to redis instance, note this waits until the connection is 'ready'.
5657
const redisConnection = await getRedisConnection({
@@ -63,23 +64,23 @@ export const handler = async () => {
6364
});
6465

6566
// capture the values for the queues
66-
const results = await Promise.all(queues.map(async(queueName) => {
67-
let depth = await getQueueDepth({redisConnection, queueName});
68-
if(!depth) {
69-
console.error(`Error fetching xlen for ${queueName}`);
70-
depth = -1;
71-
}
72-
return {queueName, depth};
73-
}));
74-
75-
if(results === undefined) {
67+
const results = await Promise.all(
68+
queueNames.map(async (queueName) => {
69+
let depth = await getQueueDepth({ redisConnection, queueName });
70+
if (!depth) {
71+
console.error(`Error fetching queue depth for ${queueName}`);
72+
depth = -1;
73+
}
74+
return { queueName, depth };
75+
}),
76+
);
77+
78+
if (results === undefined) {
7679
throw new Error('Error fetching ALL queue depths');
7780
}
7881

7982
// publish the metrics
8083
publishMetrics(results);
81-
82-
8384
} catch (error) {
8485
console.error('Error publishing metric data', error);
8586
throw error;

src/construct.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export interface RedisQueueDepthMetricPublisherProps {
2525
*/
2626
readonly publishFrequency?: Duration;
2727
/**
28-
* List of queues to measure depth for.
28+
* List of queue names to measure depth for.
2929
*/
30-
readonly queues: string[];
30+
readonly queueNames: string[];
3131
/**
3232
* Where is Redis?
3333
*/
@@ -115,7 +115,7 @@ export class RedisQueueDepthMetricPublisher extends Construct {
115115
[
116116
new PolicyStatement({
117117
sid: 'fetchRedisSecret',
118-
actions: ['WRITEME'],
118+
actions: ['secretsmanager:GetSecretValue'],
119119
resources: [props.redisSecretArn],
120120
}),
121121
new PolicyStatement({
@@ -141,12 +141,12 @@ export class RedisQueueDepthMetricPublisher extends Construct {
141141
}
142142

143143
this.handler
144-
.addEnvironment('QUEUES', JSON.stringify(props.queues))
144+
.addEnvironment('QUEUE_NAMES', JSON.stringify(props.queueNames))
145145
.addEnvironment('REDIS_ADDR', props.redisAddr)
146146
.addEnvironment('REDIS_PORT', props.redisPort ?? '6379')
147147
.addEnvironment('REDIS_SECRET_ARN', props.redisSecretArn)
148148
.addEnvironment('REDIS_SECRET_PASSWORD_PATH', props.redisSecretPasswordPath ?? 'password')
149-
.addEnvironment('REDIS_SECRET_USERNAME_PATH', props.redisSecretUsernamePath ?? 'username')
149+
.addEnvironment('REDIS_SECRET_USERNAME_PATH', props.redisSecretUsernamePath ?? 'username');
150150

151151
this.rule = new Rule(this, 'rule', {
152152
schedule: Schedule.rate(this.publishFrequency),

src/methods.ts

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,11 @@ export async function getSecrets(props: GetSecretsProps): Promise<GetSecretsResp
2323
}
2424

2525
return {
26-
username: secret[props.redisSecretUsernamePath],
27-
password: secret[props.redisSecretPasswordPath],
26+
username: secret[props.redisSecretUsernamePath as keyof typeof secret],
27+
password: secret[props.redisSecretPasswordPath as keyof typeof secret],
2828
};
2929
}
3030

31-
function getCurrentTimeMs(): number {
32-
const date = new Date();
33-
return date.getTime();
34-
}
35-
3631
export interface GetRedisConnectionProps extends RedisOptions {
3732
/**
3833
* How long to wait for the redis connection to be ready.
@@ -49,51 +44,73 @@ export interface GetRedisConnectionProps extends RedisOptions {
4944
*
5045
* @example
5146
* const redis = await getRedisConnection({
52-
* redisAddr: 'redis.example.com',
53-
* redisPort: '6379',
47+
* host: 'redis.example.com',
48+
* port: 6379,
5449
* username: 'XXXXXXXX',
5550
* password: 'XXXXXXXX',
5651
* }
57-
*/
52+
*/
5853
export async function getRedisConnection(props: GetRedisConnectionProps): Promise<Redis> {
59-
const r = await new Redis(props);
54+
const redisConnection = new Redis(props);
6055

6156
// Wait until it actually connects, or throw.
62-
const timeout = getCurrentTimeMs() + (props.maxWaitForReadyMs ?? 1000);
63-
while(r.status !== 'ready') {
64-
if (getCurrentTimeMs() > timeout) {
65-
throw new Error(`Timeout: Redis connection is ${r.status}, not 'ready'`);
57+
58+
const timeout = Date.now() + (props.maxWaitForReadyMs ?? 5000);
59+
while (redisConnection.status !== 'ready') {
60+
if (Date.now() > timeout) {
61+
throw new Error(`Timeout: Redis connection is ${redisConnection.status}, not 'ready'`);
6662
}
67-
console.debug(`Redis connection is ${r.status}, waiting...`);
68-
await new Promise(resolve => setTimeout(resolve, 100));
63+
console.debug(`Redis connection is ${redisConnection.status}, waiting...`);
64+
await new Promise((resolve) => setTimeout(resolve, 100));
6965
}
7066

71-
return r;
67+
return redisConnection;
7268
}
7369

7470
export interface GetQueueDepthProps {
7571
redisConnection: Redis;
7672
queueName: string;
7773
}
74+
7875
export async function getQueueDepth(props: GetQueueDepthProps): Promise<number> {
79-
// TODO: validate this is what we're actually doing.
80-
return await props.redisConnection.xlen(props.queueName);
81-
}
76+
let count = 0;
77+
let cursor = '0';
78+
79+
do {
80+
const result = await props.redisConnection.scan(cursor, 'MATCH', `${props.queueName}*`, 'COUNT', '1000');
81+
cursor = result[0];
82+
count += result[1].length;
83+
} while (cursor !== '0');
8284

85+
return count;
86+
}
8387

8488
export interface PublishMetricsProps {}
8589
export interface PublishMetricsPayload {
8690
queueName: string;
8791
depth: number;
8892
}
89-
export async function publishMetrics(payload: PublishMetricsPayload[], options?: MetricsOptions,): Promise<void> {
93+
export async function publishMetrics(payload: PublishMetricsPayload[], options?: MetricsOptions): Promise<void> {
9094
const metrics = new Metrics(options);
9195
payload.forEach((p) => {
9296
if (p.depth >= 0) {
93-
metrics.addMetric(p.queueName, MetricUnits.Count, p.depth)
97+
metrics.addMetric(p.queueName, MetricUnits.Count, p.depth);
9498
}
9599
});
96-
await metrics.publishStoredMetrics();
100+
metrics.publishStoredMetrics();
97101
return;
98102
}
99103

104+
async function localTest() {
105+
const conn = await getRedisConnection({
106+
host: 'localhost',
107+
});
108+
const depth = await getQueueDepth({
109+
redisConnection: conn,
110+
queueName: 'NOTIFQUEUE:NOTIFQUEUE',
111+
});
112+
113+
console.log({ depth });
114+
}
115+
116+
// localTest();

test/construct.handler.test.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { monitor } from '../src/construct.handler';
1+
import { handler } from '../src/construct.handler';
22

33
// Silence log output
44
(['log', 'error'] as jest.FunctionPropertyNames<Required<Console>>[]).forEach((func) =>
@@ -19,12 +19,12 @@ interface EnvVarTestCase {
1919
}
2020

2121
const defaultEnvVars: Record<string, any> = {
22-
'REDIS_ADDR': 'fakeAddr',
23-
'REDIS_PORT': '12345',
24-
'QUEUES': JSON.stringify(['fakeQueue']),
25-
'REDIS_SECRET_ARN': 'fakeArn',
26-
'REDIS_SECRET_PASSWORD_PATH': 'fakePasswordPath',
27-
'REDIS_SECRET_USERNAME_PATH': 'fakeUsernamePath',
22+
REDIS_ADDR: 'fakeAddr',
23+
REDIS_PORT: '12345',
24+
QUEUES: JSON.stringify(['fakeQueue']),
25+
REDIS_SECRET_ARN: 'fakeArn',
26+
REDIS_SECRET_PASSWORD_PATH: 'fakePasswordPath',
27+
REDIS_SECRET_USERNAME_PATH: 'fakeUsernamePath',
2828
};
2929

3030
describe('environment variable testing', () => {
@@ -35,17 +35,21 @@ describe('environment variable testing', () => {
3535
Object.keys(defaultEnvVars).forEach((key) => delete env[key]);
3636
}
3737

38-
beforeAll(()=> {env = process.env});
38+
beforeAll(() => {
39+
env = process.env;
40+
});
3941
beforeEach(cleanEnv);
4042
afterAll(() => {
4143
cleanEnv();
4244
process.env = env; // restore original env vars
4345
});
4446

45-
it.each<EnvVarTestCase>([{
47+
it.each<EnvVarTestCase>([
48+
{
4649
name: 'REDIS_ADDR',
4750
expectedError: 'REDIS_ADDR environment variable not set',
48-
}])('monitor errors when required environment variable', ({name, expectedError}) => {
51+
},
52+
])('handler errors when required environment variable', ({ name, expectedError }) => {
4953
// Mock in ALL the defaultEnvVars EXCEPT the one being tested
5054
Object.keys(defaultEnvVars).forEach((key) => {
5155
if (key === name) {
@@ -54,7 +58,7 @@ describe('environment variable testing', () => {
5458
process.env[key] = defaultEnvVars[key];
5559
});
5660

57-
expect(monitor()).rejects.toThrow(expectedError);
61+
expect(handler()).rejects.toThrow(expectedError);
5862
});
5963
});
6064

test/construct.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const defaultRedisQueueDepthMetricPublisherProps: RedisQueueDepthMetricPublisher
1717
cwNamespace: 'example',
1818
cloudwatchLogsRetention: RetentionDays.TWO_WEEKS,
1919
publishFrequency: Duration.seconds(5),
20-
queues: ['fakeQueue', 'fakeQueue2'],
20+
queueNames: ['fakeQueue', 'fakeQueue2'],
2121
redisAddr: 'fakeRedisAddr',
2222
redisPort: '123456',
2323
redisSecretArn: 'fakeRedisSecretArn',
@@ -39,14 +39,12 @@ function createRedisQueueDepthMetricPublisher(id: string, props?: RedisQueueDept
3939
describe.skip('RedisQueueDepthMetricPublisher', () => {
4040
it('creates resources', () => {
4141
createRedisQueueDepthMetricPublisher('defaultProps', defaultRedisQueueDepthMetricPublisherProps);
42-
template.resourceCountIs('AWS::Lambda::Function', 2); // One
42+
template.resourceCountIs('AWS::Lambda::Function', 2); // One
4343
expect(redisQueueDepthMetricPublisher.publishFrequency).toEqual(1);
4444
expect(redisQueueDepthMetricPublisher.regions).toEqual(['us-east-1']);
4545
});
4646
it('creates resources with default props', () => {
47-
createRedisQueueDepthMetricPublisher('noProps', {
48-
49-
});
47+
createRedisQueueDepthMetricPublisher('noProps');
5048
template.resourceCountIs('AWS::Lambda::Function', 2);
5149
expect(redisQueueDepthMetricPublisher.publishFrequency).toEqual(1);
5250
expect(redisQueueDepthMetricPublisher.regions).toEqual(['us-east-1']);

0 commit comments

Comments
 (0)