Skip to content

Commit 69207f3

Browse files
authored
Merge pull request #52 from Chinonso-Peter/main
feat: #17 Add Asset Filtering To GET /api/campaigns
2 parents 3e2da96 + 01713bd commit 69207f3

6 files changed

Lines changed: 342 additions & 10 deletions

File tree

backend/package-lock.json

Lines changed: 111 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"test": "vitest"
1111
},
1212
"dependencies": {
13+
"axios": "^1.13.6",
1314
"better-sqlite3": "^12.6.2",
1415
"cors": "^2.8.5",
1516
"dotenv": "^17.3.1",

backend/src/index.test.ts

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import { beforeAll, beforeEach, describe, expect, it } from "vitest";
4+
5+
const TEST_DB_PATH = path.join("/tmp", `stellar-goal-vault-campaign-filters-${process.pid}.db`);
6+
7+
process.env.DB_PATH = TEST_DB_PATH;
8+
process.env.CONTRACT_ID = "";
9+
10+
type IndexModule = typeof import("./index");
11+
type CampaignStoreModule = typeof import("./services/campaignStore");
12+
type DbModule = typeof import("./services/db");
13+
14+
let filterCampaignList: IndexModule["filterCampaignList"];
15+
let parseCampaignListFilters: IndexModule["parseCampaignListFilters"];
16+
let createCampaign: CampaignStoreModule["createCampaign"];
17+
let addPledge: CampaignStoreModule["addPledge"];
18+
let calculateProgress: CampaignStoreModule["calculateProgress"];
19+
let getDb: DbModule["getDb"];
20+
21+
const CREATOR = `G${"A".repeat(55)}`;
22+
const CONTRIBUTOR = `G${"B".repeat(55)}`;
23+
24+
beforeAll(async () => {
25+
fs.rmSync(TEST_DB_PATH, { force: true });
26+
27+
({ filterCampaignList, parseCampaignListFilters } = await import("./index"));
28+
({ createCampaign, addPledge, calculateProgress } = await import("./services/campaignStore"));
29+
({ getDb } = await import("./services/db"));
30+
});
31+
32+
beforeEach(() => {
33+
const db = getDb();
34+
db.prepare(`DELETE FROM campaign_events`).run();
35+
db.prepare(`DELETE FROM pledges`).run();
36+
db.prepare(`DELETE FROM campaigns`).run();
37+
});
38+
39+
function createCampaignFixtures() {
40+
const now = Math.floor(Date.now() / 1000);
41+
42+
const openUsdc = createCampaign({
43+
creator: CREATOR,
44+
title: "Open USDC Campaign",
45+
description: "Open USDC campaign for checking unfiltered and asset-filtered results.",
46+
assetCode: "USDC",
47+
targetAmount: 150,
48+
deadline: now + 3600,
49+
});
50+
51+
const fundedUsdcCampaign = createCampaign({
52+
creator: CREATOR,
53+
title: "Funded USDC Campaign",
54+
description: "Funded USDC campaign that should match combined asset and status filters.",
55+
assetCode: "usdc",
56+
targetAmount: 100,
57+
deadline: now + 7200,
58+
});
59+
const fundedUsdc = addPledge(fundedUsdcCampaign.id, { contributor: CONTRIBUTOR, amount: 100 });
60+
61+
const fundedXlmCampaign = createCampaign({
62+
creator: CREATOR,
63+
title: "Funded XLM Campaign",
64+
description: "Funded XLM campaign that should be excluded when asset is filtered to USDC.",
65+
assetCode: "XLM",
66+
targetAmount: 75,
67+
deadline: now + 7200,
68+
});
69+
const fundedXlm = addPledge(fundedXlmCampaign.id, { contributor: CONTRIBUTOR, amount: 75 });
70+
71+
const failedUsdc = createCampaign({
72+
creator: CREATOR,
73+
title: "Failed USDC Campaign",
74+
description: "Failed USDC campaign with a past deadline to exercise status-based filtering.",
75+
assetCode: "USDC",
76+
targetAmount: 200,
77+
deadline: now - 60,
78+
});
79+
80+
const claimedUsdcCampaign = createCampaign({
81+
creator: CREATOR,
82+
title: "Claimed USDC Campaign",
83+
description: "Claimed USDC campaign to ensure other statuses are still returned correctly.",
84+
assetCode: "USDC",
85+
targetAmount: 50,
86+
deadline: now + 7200,
87+
});
88+
const claimedUsdcFunded = addPledge(claimedUsdcCampaign.id, {
89+
contributor: CONTRIBUTOR,
90+
amount: 50,
91+
});
92+
getDb()
93+
.prepare(`UPDATE campaigns SET claimed_at = ? WHERE id = ?`)
94+
.run(now, claimedUsdcFunded.id);
95+
96+
const claimedUsdc = {
97+
...claimedUsdcFunded,
98+
claimedAt: now,
99+
};
100+
101+
return { openUsdc, fundedUsdc, fundedXlm, failedUsdc, claimedUsdc };
102+
}
103+
104+
function buildCampaignList() {
105+
const fixtures = createCampaignFixtures();
106+
const campaigns = Object.values(fixtures).map((campaign) => ({
107+
...campaign,
108+
progress: calculateProgress(campaign),
109+
}));
110+
111+
return { fixtures, campaigns };
112+
}
113+
114+
describe("campaign list filters", () => {
115+
it("filters campaigns by asset code case-insensitively", () => {
116+
const { fixtures, campaigns } = buildCampaignList();
117+
118+
const filtered = filterCampaignList(campaigns, parseCampaignListFilters({ asset: "usdc" }));
119+
120+
expect(filtered).toHaveLength(4);
121+
expect(filtered.map((campaign) => campaign.id).sort()).toEqual(
122+
[
123+
fixtures.openUsdc.id,
124+
fixtures.fundedUsdc.id,
125+
fixtures.failedUsdc.id,
126+
fixtures.claimedUsdc.id,
127+
].sort(),
128+
);
129+
expect(filtered.every((campaign) => campaign.assetCode === "USDC")).toBe(true);
130+
});
131+
132+
it("ignores invalid or empty asset filters instead of failing", () => {
133+
const { campaigns } = buildCampaignList();
134+
135+
const invalid = filterCampaignList(campaigns, parseCampaignListFilters({ asset: "doge" }));
136+
const empty = filterCampaignList(campaigns, parseCampaignListFilters({ asset: " " }));
137+
138+
expect(invalid).toHaveLength(5);
139+
expect(empty).toHaveLength(5);
140+
});
141+
142+
it("combines status and asset filtering correctly", () => {
143+
const { fixtures, campaigns } = buildCampaignList();
144+
145+
const filtered = filterCampaignList(
146+
campaigns,
147+
parseCampaignListFilters({ asset: "UsDc", status: "FuNdEd" }),
148+
);
149+
150+
expect(filtered).toHaveLength(1);
151+
expect(filtered[0].id).toBe(fixtures.fundedUsdc.id);
152+
expect(filtered[0].assetCode).toBe("USDC");
153+
expect(filtered[0].progress.status).toBe("funded");
154+
});
155+
});

0 commit comments

Comments
 (0)