Skip to content

Commit 2af4c3d

Browse files
improve package detection logic and check its usage from the npm registry
1 parent c4c4ca8 commit 2af4c3d

File tree

3 files changed

+443
-43
lines changed

3 files changed

+443
-43
lines changed

packages/wrangler/src/__tests__/metrics/data-catalog.test.ts

Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,28 @@ describe("data-catalog", () => {
4040
enabled: true,
4141
deviceId: "mock-device",
4242
});
43+
// Default npm API mock that returns high downloads so packages pass the threshold check
44+
// Individual tests can override this with msw.use() for specific scenarios
45+
// Use wildcard pattern to capture scoped packages like @cloudflare/workers-types
46+
msw.use(
47+
http.get(
48+
"https://api.npmjs.org/downloads/point/last-year/*",
49+
({ request }) => {
50+
const url = new URL(request.url);
51+
// Extract package name from URL path (handles scoped packages)
52+
const packageName = url.pathname.replace(
53+
"/downloads/point/last-year/",
54+
""
55+
);
56+
return HttpResponse.json({
57+
downloads: 100000,
58+
start: "2025-03-06",
59+
end: "2026-03-06",
60+
package: packageName,
61+
});
62+
}
63+
)
64+
);
4365
});
4466

4567
afterEach(() => {
@@ -794,6 +816,324 @@ describe("data-catalog", () => {
794816
);
795817
});
796818

819+
it("should include packages with npm downloads above threshold", async ({
820+
expect,
821+
}) => {
822+
let capturedBody: { projectDependencies?: Record<string, unknown> } = {};
823+
msw.use(
824+
http.post(TEST_DATA_CATALOG_WORKER_URL, async ({ request }) => {
825+
capturedBody = (await request.json()) as typeof capturedBody;
826+
return HttpResponse.json({ success: true });
827+
}),
828+
http.get(
829+
"https://api.npmjs.org/downloads/point/last-year/*",
830+
({ request }) => {
831+
const url = new URL(request.url);
832+
const packageName = url.pathname.replace(
833+
"/downloads/point/last-year/",
834+
""
835+
);
836+
// Return downloads above the 10,000 threshold
837+
return HttpResponse.json({
838+
downloads: 50000,
839+
start: "2025-03-06",
840+
end: "2026-03-06",
841+
package: packageName,
842+
});
843+
}
844+
)
845+
);
846+
847+
vi.mocked(getInstalledPackageJson).mockImplementation((packageName) => {
848+
if (packageName === "hono") {
849+
return { name: "hono", version: "4.0.0" };
850+
}
851+
return undefined;
852+
});
853+
854+
await seed({
855+
"package.json": JSON.stringify({
856+
name: "test-project",
857+
dependencies: {
858+
hono: "^4.0.0",
859+
},
860+
}),
861+
});
862+
863+
await sendDeploymentToTelemetryDataCatalog({
864+
accountId: TEST_ACCOUNT_ID,
865+
workerName: TEST_WORKER_NAME,
866+
projectPath: ".",
867+
bindings: {},
868+
complianceConfig: TEST_COMPLIANCE_CONFIG,
869+
});
870+
871+
// Package with downloads above threshold should be included
872+
expect(capturedBody.projectDependencies).toHaveProperty("hono");
873+
expect(capturedBody.projectDependencies?.hono).toEqual({
874+
packageJsonVersion: "^4.0.0",
875+
installedVersion: "4.0.0",
876+
});
877+
});
878+
879+
it("should exclude packages with npm downloads below threshold", async ({
880+
expect,
881+
}) => {
882+
let capturedBody: { projectDependencies?: Record<string, unknown> } = {};
883+
msw.use(
884+
http.post(TEST_DATA_CATALOG_WORKER_URL, async ({ request }) => {
885+
capturedBody = (await request.json()) as typeof capturedBody;
886+
return HttpResponse.json({ success: true });
887+
}),
888+
http.get(
889+
"https://api.npmjs.org/downloads/point/last-year/*",
890+
({ request }) => {
891+
const url = new URL(request.url);
892+
const packageName = url.pathname.replace(
893+
"/downloads/point/last-year/",
894+
""
895+
);
896+
if (packageName === "popular-pkg") {
897+
return HttpResponse.json({
898+
downloads: 50000,
899+
start: "2025-03-06",
900+
end: "2026-03-06",
901+
package: packageName,
902+
});
903+
}
904+
// Return downloads below the 10,000 threshold for unpopular package
905+
return HttpResponse.json({
906+
downloads: 500,
907+
start: "2025-03-06",
908+
end: "2026-03-06",
909+
package: packageName,
910+
});
911+
}
912+
)
913+
);
914+
915+
vi.mocked(getInstalledPackageJson).mockImplementation((packageName) => {
916+
if (packageName === "popular-pkg") {
917+
return { name: "popular-pkg", version: "1.0.0" };
918+
}
919+
if (packageName === "unpopular-pkg") {
920+
return { name: "unpopular-pkg", version: "2.0.0" };
921+
}
922+
return undefined;
923+
});
924+
925+
await seed({
926+
"package.json": JSON.stringify({
927+
name: "test-project",
928+
dependencies: {
929+
"popular-pkg": "^1.0.0",
930+
"unpopular-pkg": "^2.0.0",
931+
},
932+
}),
933+
});
934+
935+
await sendDeploymentToTelemetryDataCatalog({
936+
accountId: TEST_ACCOUNT_ID,
937+
workerName: TEST_WORKER_NAME,
938+
projectPath: ".",
939+
bindings: {},
940+
complianceConfig: TEST_COMPLIANCE_CONFIG,
941+
});
942+
943+
// Package with downloads above threshold should be included
944+
expect(capturedBody.projectDependencies).toHaveProperty("popular-pkg");
945+
946+
// Package with downloads below threshold should be excluded
947+
expect(capturedBody.projectDependencies).not.toHaveProperty(
948+
"unpopular-pkg"
949+
);
950+
});
951+
952+
it("should exclude packages when npm API returns an error", async ({
953+
expect,
954+
}) => {
955+
let capturedBody: { projectDependencies?: Record<string, unknown> } = {};
956+
msw.use(
957+
http.post(TEST_DATA_CATALOG_WORKER_URL, async ({ request }) => {
958+
capturedBody = (await request.json()) as typeof capturedBody;
959+
return HttpResponse.json({ success: true });
960+
}),
961+
http.get(
962+
"https://api.npmjs.org/downloads/point/last-year/*",
963+
({ request }) => {
964+
const url = new URL(request.url);
965+
const packageName = url.pathname.replace(
966+
"/downloads/point/last-year/",
967+
""
968+
);
969+
if (packageName === "valid-pkg") {
970+
return HttpResponse.json({
971+
downloads: 50000,
972+
start: "2025-03-06",
973+
end: "2026-03-06",
974+
package: packageName,
975+
});
976+
}
977+
// Return 404 for unknown package
978+
return new HttpResponse(null, { status: 404 });
979+
}
980+
)
981+
);
982+
983+
vi.mocked(getInstalledPackageJson).mockImplementation((packageName) => {
984+
if (packageName === "valid-pkg") {
985+
return { name: "valid-pkg", version: "1.0.0" };
986+
}
987+
if (packageName === "unknown-pkg") {
988+
return { name: "unknown-pkg", version: "1.0.0" };
989+
}
990+
return undefined;
991+
});
992+
993+
await seed({
994+
"package.json": JSON.stringify({
995+
name: "test-project",
996+
dependencies: {
997+
"valid-pkg": "^1.0.0",
998+
"unknown-pkg": "^1.0.0",
999+
},
1000+
}),
1001+
});
1002+
1003+
await sendDeploymentToTelemetryDataCatalog({
1004+
accountId: TEST_ACCOUNT_ID,
1005+
workerName: TEST_WORKER_NAME,
1006+
projectPath: ".",
1007+
bindings: {},
1008+
complianceConfig: TEST_COMPLIANCE_CONFIG,
1009+
});
1010+
1011+
// Valid package should be included
1012+
expect(capturedBody.projectDependencies).toHaveProperty("valid-pkg");
1013+
1014+
// Package that returns 404 from npm API should be excluded
1015+
expect(capturedBody.projectDependencies).not.toHaveProperty(
1016+
"unknown-pkg"
1017+
);
1018+
});
1019+
1020+
it("should handle npm API network failures gracefully", async ({
1021+
expect,
1022+
}) => {
1023+
let capturedBody: { projectDependencies?: Record<string, unknown> } = {};
1024+
msw.use(
1025+
http.post(TEST_DATA_CATALOG_WORKER_URL, async ({ request }) => {
1026+
capturedBody = (await request.json()) as typeof capturedBody;
1027+
return HttpResponse.json({ success: true });
1028+
}),
1029+
http.get("https://api.npmjs.org/downloads/point/last-year/*", () => {
1030+
// Simulate network error
1031+
return HttpResponse.error();
1032+
})
1033+
);
1034+
1035+
vi.mocked(getInstalledPackageJson).mockImplementation((packageName) => {
1036+
if (packageName === "some-pkg") {
1037+
return { name: "some-pkg", version: "1.0.0" };
1038+
}
1039+
return undefined;
1040+
});
1041+
1042+
await seed({
1043+
"package.json": JSON.stringify({
1044+
name: "test-project",
1045+
dependencies: {
1046+
"some-pkg": "^1.0.0",
1047+
},
1048+
}),
1049+
});
1050+
1051+
// Should not throw
1052+
await sendDeploymentToTelemetryDataCatalog({
1053+
accountId: TEST_ACCOUNT_ID,
1054+
workerName: TEST_WORKER_NAME,
1055+
projectPath: ".",
1056+
bindings: {},
1057+
complianceConfig: TEST_COMPLIANCE_CONFIG,
1058+
});
1059+
1060+
// Package should be excluded when npm API fails
1061+
expect(capturedBody.projectDependencies).not.toHaveProperty("some-pkg");
1062+
});
1063+
1064+
it("should exclude packages when npm API returns mismatched package name", async ({
1065+
expect,
1066+
}) => {
1067+
let capturedBody: { projectDependencies?: Record<string, unknown> } = {};
1068+
msw.use(
1069+
http.post(TEST_DATA_CATALOG_WORKER_URL, async ({ request }) => {
1070+
capturedBody = (await request.json()) as typeof capturedBody;
1071+
return HttpResponse.json({ success: true });
1072+
}),
1073+
http.get(
1074+
"https://api.npmjs.org/downloads/point/last-year/*",
1075+
({ request }) => {
1076+
const url = new URL(request.url);
1077+
const packageName = url.pathname.replace(
1078+
"/downloads/point/last-year/",
1079+
""
1080+
);
1081+
if (packageName === "correct-pkg") {
1082+
return HttpResponse.json({
1083+
downloads: 50000,
1084+
start: "2025-03-06",
1085+
end: "2026-03-06",
1086+
package: packageName,
1087+
});
1088+
}
1089+
// Return response with mismatched package name
1090+
return HttpResponse.json({
1091+
downloads: 50000,
1092+
start: "2025-03-06",
1093+
end: "2026-03-06",
1094+
package: "different-package-name",
1095+
});
1096+
}
1097+
)
1098+
);
1099+
1100+
vi.mocked(getInstalledPackageJson).mockImplementation((packageName) => {
1101+
if (packageName === "correct-pkg") {
1102+
return { name: "correct-pkg", version: "1.0.0" };
1103+
}
1104+
if (packageName === "mismatched-pkg") {
1105+
return { name: "mismatched-pkg", version: "1.0.0" };
1106+
}
1107+
return undefined;
1108+
});
1109+
1110+
await seed({
1111+
"package.json": JSON.stringify({
1112+
name: "test-project",
1113+
dependencies: {
1114+
"correct-pkg": "^1.0.0",
1115+
"mismatched-pkg": "^1.0.0",
1116+
},
1117+
}),
1118+
});
1119+
1120+
await sendDeploymentToTelemetryDataCatalog({
1121+
accountId: TEST_ACCOUNT_ID,
1122+
workerName: TEST_WORKER_NAME,
1123+
projectPath: ".",
1124+
bindings: {},
1125+
complianceConfig: TEST_COMPLIANCE_CONFIG,
1126+
});
1127+
1128+
// Package with correct response should be included
1129+
expect(capturedBody.projectDependencies).toHaveProperty("correct-pkg");
1130+
1131+
// Package with mismatched name in response should be excluded
1132+
expect(capturedBody.projectDependencies).not.toHaveProperty(
1133+
"mismatched-pkg"
1134+
);
1135+
});
1136+
7971137
it("should skip execution for fedramp_high compliance region when env URL is not set", async ({
7981138
expect,
7991139
}) => {

0 commit comments

Comments
 (0)