Skip to content

Commit 5828aca

Browse files
committed
read config from ssm
1 parent 1b3e319 commit 5828aca

File tree

10 files changed

+67
-210
lines changed

10 files changed

+67
-210
lines changed

ab-testing/dictionary-deploy-lambda/src/index.ts

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,8 @@ import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
22
import type { Handler } from "aws-cdk-lib/aws-lambda";
33
import type { CloudFormationCustomResourceEvent, Context } from "aws-lambda";
44
import { send } from "cfn-response";
5-
import {
6-
abTestsDictionaryId,
7-
abTestsDictionaryName,
8-
mvtDictionaryId,
9-
mvtDictionaryName,
10-
serviceId,
11-
serviceName,
12-
} from "../../lib/config.ts";
5+
import { assert } from "superstruct";
6+
import { configStruct } from "../../lib/config.ts";
137
import { FastlyClient } from "../../lib/fastly/client.ts";
148
import { fetchAndDeployArtifacts } from "./deploy.ts";
159

@@ -25,6 +19,18 @@ const getSecureString = async (name: string) => {
2519
return response.Parameter?.Value;
2620
};
2721

22+
const getFastlyConfig = async () => {
23+
const stringParam = await getSecureString(
24+
`/ab-testing-deploy/${process.env.STAGE}/fastly-ab-testing-config`,
25+
);
26+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- empty string is invalid JSON too
27+
const json = JSON.parse(stringParam || "{}") as unknown;
28+
29+
assert(json, configStruct);
30+
31+
return json;
32+
};
33+
2834
export const handler: Handler = async (
2935
event: CloudFormationCustomResourceEvent,
3036
context: Context,
@@ -37,6 +43,15 @@ export const handler: Handler = async (
3743
throw new Error("Fastly API token not found in SSM Parameter Store");
3844
}
3945

46+
const {
47+
serviceId,
48+
serviceName,
49+
abTestsDictionaryId,
50+
abTestsDictionaryName,
51+
mvtDictionaryId,
52+
mvtDictionaryName,
53+
} = await getFastlyConfig();
54+
4055
const fastly = new FastlyClient(apiToken);
4156
const service = await fastly.getService(serviceId, serviceName);
4257

ab-testing/lib/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,5 @@ export {
4040
mvtDictionaryName,
4141
abTestsDictionaryId,
4242
abTestsDictionaryName,
43+
configStruct,
4344
};

ab-testing/lib/fastly/client.test.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,7 @@
11
import { deepEqual, equal, match, rejects } from "node:assert";
22
import type { Mock } from "node:test";
33
import test, { describe, mock } from "node:test";
4-
5-
const mockConfig = {
6-
serviceName: "test-service",
7-
serviceId: "test-service-id",
8-
mvtDictionaryId: "test-mvt-dictionary-id",
9-
mvtDictionaryName: "test-mvt-dictionary",
10-
abTestsDictionaryId: "test-ab-tests-dictionary-id",
11-
abTestsDictionaryName: "test-ab-tests-dictionary",
12-
};
13-
14-
// Mock environment variables
15-
process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig);
16-
process.env.FASTLY_API_TOKEN = "test-api-token";
4+
import { FastlyClient } from "./client.ts";
175

186
type MockedFetch = Mock<typeof fetch>;
197

@@ -27,9 +15,6 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") {
2715
globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse));
2816
}
2917

30-
// Import after mocking
31-
const { FastlyClient } = await import("./client.ts");
32-
3318
describe("FastlyClient", async () => {
3419
await test("fetch - successfully fetches data", async () => {
3520
const mockResponse = { data: "test" };
@@ -160,6 +145,7 @@ describe("FastlyClient", async () => {
160145
const client = new FastlyClient("test-api-token");
161146
const result = await client.getDictionaryItems({
162147
dictionaryId: "test-dict",
148+
serviceId: "service-123",
163149
});
164150

165151
deepEqual(result, mockResponse);
@@ -181,6 +167,7 @@ describe("FastlyClient", async () => {
181167
async () => {
182168
await client.getDictionaryItems({
183169
dictionaryId: "test-dict",
170+
serviceId: "service-123",
184171
});
185172
},
186173
Error,
@@ -197,6 +184,7 @@ describe("FastlyClient", async () => {
197184
];
198185
await client.updateDictionaryItems({
199186
dictionaryId: "dict-123",
187+
serviceId: "service-123",
200188
items,
201189
});
202190

@@ -233,6 +221,7 @@ describe("FastlyClient", async () => {
233221
async () => {
234222
await client.updateDictionaryItems({
235223
dictionaryId: "dict-123",
224+
serviceId: "service-123",
236225
items,
237226
});
238227
},

ab-testing/lib/fastly/client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
string,
99
type,
1010
} from "superstruct";
11-
import { apiToken, serviceId } from "../config.ts";
1211
import { FastlyService } from "./service.ts";
1312

1413
const dictionaryItemStruct = object({
@@ -72,7 +71,7 @@ export class FastlyClient {
7271
...options,
7372
headers: {
7473
...options.headers,
75-
"Fastly-Key": apiToken,
74+
"Fastly-Key": this.apiToken,
7675
},
7776
});
7877
if (!response.ok) {
@@ -150,8 +149,10 @@ export class FastlyClient {
150149

151150
async getDictionaryItems({
152151
dictionaryId,
152+
serviceId,
153153
}: {
154154
dictionaryId: string;
155+
serviceId: string;
155156
}): Promise<DictionaryItem[]> {
156157
const dictionary = await this.fetch(
157158
`${serviceId}/dictionary/${dictionaryId}/items?per_page=1000`,
@@ -164,9 +165,11 @@ export class FastlyClient {
164165

165166
async updateDictionaryItems({
166167
dictionaryId,
168+
serviceId,
167169
items,
168170
}: {
169171
dictionaryId: string;
172+
serviceId: string;
170173
items: UpdateDictionaryItemRequest[];
171174
}): Promise<{ status: string }> {
172175
const dictionary = await this.fetch(
Lines changed: 4 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import { deepEqual, equal, match } from "node:assert";
1+
import { deepEqual, equal } from "node:assert";
22
import type { Mock } from "node:test";
33
import test, { describe, mock } from "node:test";
4-
5-
const mockConfig = {
6-
serviceName: "test-service",
7-
serviceId: "test-service-id",
8-
mvtDictionaryId: "test-mvt-dictionary-id",
9-
mvtDictionaryName: "test-mvt-dictionary",
10-
abTestsDictionaryId: "test-ab-tests-dictionary-id",
11-
abTestsDictionaryName: "test-ab-tests-dictionary",
12-
};
13-
14-
// Mock environment variables
15-
process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig);
16-
process.env.FASTLY_API_TOKEN = "test-api-token";
4+
import { FastlyClient } from "./client.ts";
5+
import { FastlyDictionary } from "./dictionary.ts";
6+
import { FastlyService } from "./service.ts";
177

188
type MockedFetch = Mock<typeof fetch>;
199

@@ -27,18 +17,6 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") {
2717
globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse));
2818
}
2919

30-
// Import after mocking
31-
const { FastlyClient } = await import("./client.ts");
32-
const {
33-
getMVTGroupsFromDictionary,
34-
getABTestGroupsFromDictionary,
35-
updateMVTGroups,
36-
updateABTestGroups,
37-
} = await import("./dictionary.ts");
38-
39-
const { FastlyService } = await import("./service.ts");
40-
const { FastlyDictionary } = await import("./dictionary.ts");
41-
4220
describe("FastlyDictionary", async () => {
4321
await test("getItems - calls service.getDictionaryItems", async () => {
4422
const mockResponse = [
@@ -91,93 +69,4 @@ describe("FastlyDictionary", async () => {
9169

9270
equal((globalThis.fetch as MockedFetch).mock.calls.length, 1);
9371
});
94-
95-
test("getMVTGroupsFromDictionary - calls the right endpoint", async () => {
96-
const mockResponse = [] as unknown;
97-
mockFetch(mockResponse);
98-
99-
const client = new FastlyClient("test-api-token");
100-
await getMVTGroupsFromDictionary(client);
101-
102-
equal((globalThis.fetch as MockedFetch).mock.calls.length, 1);
103-
match(
104-
(globalThis.fetch as MockedFetch).mock.calls[0]
105-
?.arguments[0] as string,
106-
new RegExp(`/dictionary/${mockConfig.mvtDictionaryId}/items`),
107-
);
108-
});
109-
110-
test("getABTestGroupsFromDictionary - calls the right endpoint", async () => {
111-
const mockResponse = [] as unknown;
112-
mockFetch(mockResponse);
113-
114-
const client = new FastlyClient("test-api-token");
115-
await getABTestGroupsFromDictionary(client);
116-
117-
equal((globalThis.fetch as MockedFetch).mock.calls.length, 1);
118-
match(
119-
(globalThis.fetch as MockedFetch).mock.calls[0]
120-
?.arguments[0] as string,
121-
new RegExp(`/dictionary/${mockConfig.abTestsDictionaryId}/items`),
122-
);
123-
});
124-
125-
test("updateMVTGroups - makes PATCH request with correct data", async () => {
126-
mockFetch({ status: "ok" });
127-
128-
const client = new FastlyClient("test-api-token");
129-
const items = [
130-
{ item_key: "key1", item_value: "value1", op: "create" as const },
131-
];
132-
await updateMVTGroups(client, items);
133-
134-
equal((globalThis.fetch as MockedFetch).mock.calls.length, 1);
135-
equal(
136-
(globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]
137-
?.method,
138-
"PATCH",
139-
);
140-
141-
const requestBody = JSON.parse(
142-
(globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]
143-
?.body as string,
144-
) as {
145-
items: Array<{
146-
item_key: string;
147-
item_value: string;
148-
op: "create" | "update" | "delete";
149-
}>;
150-
};
151-
152-
deepEqual(requestBody.items, items);
153-
});
154-
155-
test("updateABTestGroups - makes PATCH request with correct data", async () => {
156-
mockFetch({ status: "ok" });
157-
158-
const client = new FastlyClient("test-api-token");
159-
const items = [
160-
{ item_key: "key1", item_value: "value1", op: "update" as const },
161-
];
162-
await updateABTestGroups(client, items);
163-
164-
equal((globalThis.fetch as MockedFetch).mock.calls.length, 1);
165-
equal(
166-
(globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]
167-
?.method,
168-
"PATCH",
169-
);
170-
171-
const requestBody = JSON.parse(
172-
(globalThis.fetch as MockedFetch).mock.calls[0]?.arguments[1]
173-
?.body as string,
174-
) as {
175-
items: Array<{
176-
item_key: string;
177-
item_value: string;
178-
op: "create" | "update" | "delete";
179-
}>;
180-
};
181-
deepEqual(requestBody.items, items);
182-
});
18372
});
Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { abTestsDictionaryId, mvtDictionaryId } from "../config.ts";
2-
import type { FastlyClient, UpdateDictionaryItemRequest } from "./client.ts";
1+
import type { UpdateDictionaryItemRequest } from "./client.ts";
32
import type { FastlyService } from "./service.ts";
43

54
export class FastlyDictionary {
@@ -24,31 +23,3 @@ export class FastlyDictionary {
2423
return this.service.updateDictionaryItems(this.id, items);
2524
}
2625
}
27-
28-
export const getMVTGroupsFromDictionary = (client: FastlyClient) =>
29-
client.getDictionaryItems({
30-
dictionaryId: mvtDictionaryId,
31-
});
32-
33-
export const getABTestGroupsFromDictionary = (client: FastlyClient) =>
34-
client.getDictionaryItems({
35-
dictionaryId: abTestsDictionaryId,
36-
});
37-
38-
export const updateMVTGroups = (
39-
client: FastlyClient,
40-
items: UpdateDictionaryItemRequest[],
41-
) =>
42-
client.updateDictionaryItems({
43-
dictionaryId: mvtDictionaryId,
44-
items,
45-
});
46-
47-
export const updateABTestGroups = (
48-
client: FastlyClient,
49-
items: UpdateDictionaryItemRequest[],
50-
) =>
51-
client.updateDictionaryItems({
52-
dictionaryId: abTestsDictionaryId,
53-
items,
54-
});

ab-testing/lib/fastly/service.test.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import { deepEqual, equal } from "node:assert";
22
import type { Mock } from "node:test";
33
import test, { describe, mock } from "node:test";
4+
import { FastlyClient } from "./client.ts";
45
import { FastlyDictionary } from "./dictionary.ts";
5-
6-
const mockConfig = {
7-
serviceName: "test-service",
8-
serviceId: "test-service-id",
9-
mvtDictionaryId: "test-mvt-dictionary-id",
10-
mvtDictionaryName: "test-mvt-dictionary",
11-
abTestsDictionaryId: "test-ab-tests-dictionary-id",
12-
abTestsDictionaryName: "test-ab-tests-dictionary",
13-
};
14-
15-
// Mock environment variables
16-
process.env.FASTLY_AB_TESTING_CONFIG = JSON.stringify(mockConfig);
17-
process.env.FASTLY_API_TOKEN = "test-api-token";
6+
import { FastlyService } from "./service.ts";
187

198
type MockedFetch = Mock<typeof fetch>;
209

@@ -28,12 +17,8 @@ function mockFetch(response: unknown, status = 200, statusText = "OK") {
2817
globalThis.fetch = mock.fn(async () => Promise.resolve(mockResponse));
2918
}
3019

31-
// Import after mocking
32-
const { FastlyClient } = await import("./client.ts");
33-
const { FastlyService } = await import("./service.ts");
34-
3520
describe("FastlyService", async () => {
36-
await test("getDictionary - returns FastlyDictionary instance", async () => {
21+
await test.only("getDictionary - returns FastlyDictionary instance", async () => {
3722
const mockResponse = {
3823
id: "dict-123",
3924
name: "test-dictionary",

ab-testing/lib/fastly/service.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,21 @@ export class FastlyService {
3838
}
3939

4040
async getDictionaryItems(dictionaryId: string) {
41-
return this.client.getDictionaryItems({ dictionaryId });
41+
return this.client.getDictionaryItems({
42+
dictionaryId,
43+
serviceId: this.id,
44+
});
4245
}
4346

4447
async updateDictionaryItems(
4548
dictionaryId: string,
4649
items: UpdateDictionaryItemRequest[],
4750
) {
48-
return this.client.updateDictionaryItems({ dictionaryId, items });
51+
return this.client.updateDictionaryItems({
52+
dictionaryId,
53+
serviceId: this.id,
54+
items,
55+
});
4956
}
5057

5158
async verifyDictionaryName({

0 commit comments

Comments
 (0)