diff --git a/contracts/ApplicationManager.sol b/contracts/ApplicationManager.sol index 9089eb6..d81d468 100644 --- a/contracts/ApplicationManager.sol +++ b/contracts/ApplicationManager.sol @@ -13,11 +13,11 @@ contract ApplicationManager is IApplicationManager { function applicationExists(uint id) internal view returns (bool) { return applications[id].account != address(0); } - function getNextApplicationId() external view override returns (uint) { + function getNextApplicationId() external view returns (uint) { return nextApplicationId; } - function createApplication(Application memory application) external override { + function createApplication(Application memory application) external { require(!addressUsed[application.account], "Address already used for another application"); applications[nextApplicationId] = application; addressUsed[application.account] = true; @@ -25,7 +25,7 @@ contract ApplicationManager is IApplicationManager { nextApplicationId++; } - function updateApplication(uint id, Application memory application) external override { + function updateApplication(uint id, Application memory application) external { require(applicationExists(id), "Application does not exist"); require(!addressUsed[application.account] || applications[id].account == application.account, "Address already used for another application"); @@ -33,20 +33,32 @@ contract ApplicationManager is IApplicationManager { emit ApplicationUpdated(id, applications[id]); } - function deleteApplication(uint id) external override { + function deleteApplication(uint id) external { require(applicationExists(id), "Application does not exist"); addressUsed[applications[id].account] = false; emit ApplicationDeleted(id, applications[id]); delete applications[id]; } - function getApplication(uint id) external view override returns (Application memory) { + function getApplication(uint id) external view returns (Application memory) { require(applicationExists(id), "Application does not exist"); return applications[id]; } - function getApplications(uint start, uint limit) external view override returns (Application[] memory) { - } + function getApplications(uint start, uint limit) external view returns (Application[] memory) { + Application[] memory result = new Application[](limit); + uint count = 0; + uint index = start; + while (count < limit && index < nextApplicationId) { + if (applicationExists(index)) { + result[count] = applications[index]; + count++; + } + index++; + } + assembly { mstore(result, count) } + return result; + } } diff --git a/test/ApplicationManager.ts b/test/ApplicationManager.ts index 95f5809..2f0ba07 100644 --- a/test/ApplicationManager.ts +++ b/test/ApplicationManager.ts @@ -1,9 +1,8 @@ import { loadFixture } from "@nomicfoundation/hardhat-toolbox-viem/network-helpers"; import { expect } from "chai"; import hre from "hardhat"; -import { type Address, getAddress, parseUnits, } from "viem"; -import { generatePrivateKey,privateKeyToAccount } from 'viem/accounts' - +import { type Address, getAddress, parseUnits } from "viem"; +import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"; interface Application { name: string; @@ -33,10 +32,10 @@ describe("ApplicationManager", () => { } function generateRandomAddress(): string { - const randomKey = generatePrivateKey(); - const account = privateKeyToAccount(randomKey); - return account.address; - } + const randomKey = generatePrivateKey(); + const account = privateKeyToAccount(randomKey); + return account.address; + } describe("Deployment", () => { it("Should set the nextxApplicationId to 0", async () => { @@ -51,7 +50,7 @@ describe("ApplicationManager", () => { // biome-ignore lint/suspicious/noExplicitAny: let contract: any; let app: Application; - let account= generateRandomAddress() as Address; + const account = generateRandomAddress() as Address; beforeEach(async () => { const fixture = await loadFixture(deploy); contract = fixture.contract; @@ -78,12 +77,12 @@ describe("ApplicationManager", () => { app, ); }); - it("Should revert if the account address is already used", async () => { - await contract.write.createApplication([app]); - await expect(contract.write.createApplication([app])).to.be.rejectedWith( - "Address already used for another application" - ); - }); + it("Should revert if the account address is already used", async () => { + await contract.write.createApplication([app]); + await expect(contract.write.createApplication([app])).to.be.rejectedWith( + "Address already used for another application", + ); + }); it("Should emit the ApplicationCreated event with the new application's details", async () => { const applicationId = await contract.read.getNextApplicationId(); const txHash = await contract.write.createApplication([app]); @@ -105,7 +104,7 @@ describe("ApplicationManager", () => { let contract: any; let app: Application; let applicationId: string; - let account= generateRandomAddress() as Address; + const account = generateRandomAddress() as Address; beforeEach(async () => { const fixture = await loadFixture(deploy); @@ -137,24 +136,29 @@ describe("ApplicationManager", () => { it("Should revert if the account address is already used", async () => { const t1 = await contract.read.getNextApplicationId(); expect(t1).to.equal(parseUnits("1", 0)); - await contract.write.createApplication([{ - account: generateRandomAddress() as Address, - name: "new app" - }]); - - await expect(contract.write.updateApplication([t1, app])).to.be.rejectedWith( - "Address already used for another application" - ); - }); - it("Should revert if the application does not exist", async () => { + await contract.write.createApplication([ + { + account: generateRandomAddress() as Address, + name: "new app", + }, + ]); + + await expect( + contract.write.updateApplication([t1, app]), + ).to.be.rejectedWith("Address already used for another application"); + }); + it("Should revert if the application does not exist", async () => { const nonExistentId = parseUnits("999", 0); - await expect(contract.write.updateApplication([nonExistentId, app])).to.be.rejectedWith( - "Application does not exist" - ); - }); + await expect( + contract.write.updateApplication([nonExistentId, app]), + ).to.be.rejectedWith("Application does not exist"); + }); it("Should emit the ApplicationUpdated event with the updated application's details", async () => { - const txHash = await contract.write.updateApplication([applicationId, app]); + const txHash = await contract.write.updateApplication([ + applicationId, + app, + ]); const events = await contract.getEvents.ApplicationUpdated(); expect(events.length).to.be.equal(1); @@ -171,7 +175,7 @@ describe("ApplicationManager", () => { let contract: any; let app: Application; let applicationId: string; - let account= privateKeyToAccount(generatePrivateKey()).address + const account = privateKeyToAccount(generatePrivateKey()).address; beforeEach(async () => { const fixture = await loadFixture(deploy); @@ -192,20 +196,22 @@ describe("ApplicationManager", () => { }); it("Should delete the application from the applications mapping with the provided applicationId", async () => { await contract.write.deleteApplication([applicationId]); - await expect(contract.read.getApplication([applicationId])).to.be.rejectedWith("Application does not exist"); + await expect( + contract.read.getApplication([applicationId]), + ).to.be.rejectedWith("Application does not exist"); }); - it("Should revert if the application does not exist", async () => { + it("Should revert if the application does not exist", async () => { const nonExistentId = parseUnits("999", 0); - await expect(contract.write.deleteApplication([nonExistentId])).to.be.rejectedWith( - "Application does not exist" - ); - }); + await expect( + contract.write.deleteApplication([nonExistentId]), + ).to.be.rejectedWith("Application does not exist"); + }); it("Should emit the ApplicationDeleted event with the deleted application's details", async () => { const txHash = await contract.write.deleteApplication([applicationId]); - const events = await contract.getEvents.ApplicationDeleted(); - expect(events.length).to.be.equal(1); + const events = await contract.getEvents.ApplicationDeleted(); + expect(events.length).to.be.equal(1); const event = events[0]; expect(event.eventName).to.be.equal("ApplicationDeleted"); @@ -215,13 +221,12 @@ describe("ApplicationManager", () => { }); }); - describe("getApplication", () => { // biome-ignore lint/suspicious/noExplicitAny: let contract: any; let app: Application; let applicationId: string; - let account= privateKeyToAccount(generatePrivateKey()).address + const account = privateKeyToAccount(generatePrivateKey()).address; beforeEach(async () => { const fixture = await loadFixture(deploy); @@ -237,17 +242,86 @@ describe("ApplicationManager", () => { }); it("Should accept id as input", async () => { - expect(await contract.write.getApplication([applicationId])).to.be - .string; + expect(await contract.write.getApplication([applicationId])).to.be.string; }); it("Should return the application from the applications mapping with the provided applicationId", async () => { - expect(await contract.read.getApplication([applicationId])).to.contains(app); + expect(await contract.read.getApplication([applicationId])).to.contains( + app, + ); }); - it("Should revert if the application does not exist", async () => { + it("Should revert if the application does not exist", async () => { const nonExistentId = parseUnits("999", 0); - await expect( contract.read.getApplication([nonExistentId])).to.be.rejectedWith( - "Application does not exist" - ); - }); + await expect( + contract.read.getApplication([nonExistentId]), + ).to.be.rejectedWith("Application does not exist"); + }); + }); + + describe("getApplications", () => { + // biome-ignore lint/suspicious/noExplicitAny: + let contract: any; + let apps: Application[]; + // biome-ignore lint/suspicious/noExplicitAny: + let fixture: any; + + beforeEach(async () => { + fixture = await loadFixture(deploy); + contract = fixture.contract; + apps = []; + for (let i = 0; i < 8; i++) { + const app = { + name: `app${i}`, + account: generateRandomAddress() as Address, + }; + await contract.write.createApplication([app]); + apps.push(app); + } + }); + + it("Should accept start and limit as inputs", async () => { + const start = parseUnits("0", 0); + const limit = parseUnits("7", 0); + + expect(await contract.write.getApplications([start, limit])).to.be.string; + }); + + it("Should return an array of Application structs starting from the start index and containing up to limit entries.", async () => { + const start = parseUnits("0", 0); + const limit = parseUnits("6", 0); + const returnedApps = await contract.read.getApplications([start, limit]); + expect(returnedApps.length).to.equal(6); + expect(returnedApps).to.deep.include.members([ + apps[0], + apps[1], + apps[2], + apps[3], + apps[4], + apps[5], + ]); + }); + + it("Should return fewer than limit entries if there are not enough applications", async () => { + await contract.write.deleteApplication([parseUnits("7", 0)]); + const start = parseUnits("6", 0); + const limit = parseUnits("2", 0); + const returnedApps = await contract.read.getApplications([start, limit]); + expect(returnedApps.length).to.equal(1); + expect(returnedApps).to.deep.include.members([apps[6]]); + }); + + it("Should return no applications if all within range are deleted", async () => { + await contract.write.deleteApplication([parseUnits("0", 0)]); + await contract.write.deleteApplication([parseUnits("2", 0)]); + await contract.write.deleteApplication([parseUnits("1", 0)]); + await contract.write.deleteApplication([parseUnits("3", 0)]); + await contract.write.deleteApplication([parseUnits("4", 0)]); + await contract.write.deleteApplication([parseUnits("5", 0)]); + await contract.write.deleteApplication([parseUnits("6", 0)]); + await contract.write.deleteApplication([parseUnits("7", 0)]); + const start = parseUnits("0", 0); + const limit = parseUnits("7", 0); + const returnedApps = await contract.read.getApplications([start, limit]); + expect(returnedApps.length).to.equal(0); + }); }); });