Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UT for VoucherTypeCreated event #22

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
- voucherCreated
- AccountAuthorized, AccountUnauthorized (#20)
- VoucherTypeDurationUpdated, VoucherTypeDescriptionUpdated (#21)
- VoucherTypeCreated (#22)

## v1.0.0
5 changes: 5 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ type Role @entity {
accounts: [Account!]! @derivedFrom(field: "role")
}

type Counter @entity {
id: ID!
count: BigInt!
}

type VoucherType @entity {
id: ID!
description: String!
Expand Down
36 changes: 30 additions & 6 deletions src/voucherHub.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BigInt, log } from '@graphprotocol/graph-ts';
import { AppRegistry } from '../generated/VoucherHub/AppRegistry';
import { DatasetRegistry } from '../generated/VoucherHub/DatasetRegistry';
import { PoCo } from '../generated/VoucherHub/PoCo';
Expand All @@ -17,7 +18,7 @@ import {
VoucherTypeDurationUpdated,
} from '../generated/VoucherHub/VoucherHub';
import { WorkerpoolRegistry } from '../generated/VoucherHub/WorkerpoolRegistry';
import { Voucher, VoucherCreation, VoucherTopUp, VoucherType } from '../generated/schema';
import { Counter, Voucher, VoucherCreation, VoucherTopUp, VoucherType } from '../generated/schema';
import { Voucher as VoucherTemplate } from '../generated/templates';
import {
getEventId,
Expand Down Expand Up @@ -184,17 +185,40 @@ export function handleVoucherToppedUp(event: VoucherToppedUp): void {
}

export function handleVoucherTypeCreated(event: VoucherTypeCreated): void {
let id = event.params.id.toString();
let id = event.params.id;
let idString = id.toString();

// Load or create counter
let counter = Counter.load('VoucherType');
if (!counter) {
counter = new Counter('VoucherType');
counter.count = BigInt.fromI32(0);
}

let current = counter.count.plus(BigInt.fromI32(1));
// Check if id matches the current count
if (!id.equals(current)) {
log.error('VoucherType ID {} does not match the current count {}', [
idString,
current.toString(),
]);
return;
}

let description = event.params.description;
let duration = event.params.duration;
let voucherType = VoucherType.load(id);
let voucherType = VoucherType.load(idString);
if (!voucherType) {
voucherType = new VoucherType(id);
voucherType = new VoucherType(idString);
voucherType.eligibleAssets = [];
voucherType.description = description;
voucherType.duration = duration;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
voucherType.description = description;
voucherType.duration = duration;
voucherType.save();

// Increment counter
counter.count = counter.count.plus(BigInt.fromI32(1));
counter.save();
}

export function handleVoucherTypeDescriptionUpdated(event: VoucherTypeDescriptionUpdated): void {
Expand Down
45 changes: 45 additions & 0 deletions tests/unit/utils/EventParamBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts';

export class EventParam {
constructor(
public key: string,
public value: ethereum.Value,
) {}
}

export class EventParamBuilder {
private params: EventParam[] = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not:

Suggested change
private params: EventParam[] = [];
private params: ethereum.EventParam[] = new Array<ethereum.EventParam>();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated here


static init(): EventParamBuilder {
return new EventParamBuilder();
}

address(key: string, value: Address): EventParamBuilder {
this.params.push(new EventParam(key, ethereum.Value.fromAddress(value)));
return this;
}

bytes(key: string, value: Bytes): EventParamBuilder {
this.params.push(new EventParam(key, ethereum.Value.fromBytes(value)));
return this;
}

bigInt(key: string, value: BigInt): EventParamBuilder {
this.params.push(new EventParam(key, ethereum.Value.fromUnsignedBigInt(value)));
return this;
}

string(key: string, value: string): EventParamBuilder {
this.params.push(new EventParam(key, ethereum.Value.fromString(value)));
return this;
}

build(event: ethereum.Event): void {
event.parameters = new Array<ethereum.EventParam>();
for (let i = 0; i < this.params.length; i++) {
event.parameters.push(
new ethereum.EventParam(this.params[i].key, this.params[i].value),
);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
build(event: ethereum.Event): void {
event.parameters = new Array<ethereum.EventParam>();
for (let i = 0; i < this.params.length; i++) {
event.parameters.push(
new ethereum.EventParam(this.params[i].key, this.params[i].value),
);
}
}
build(): ethereum.EventParam[] {
return this.params;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated here

}
84 changes: 49 additions & 35 deletions tests/unit/utils/utils.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { Address, BigInt, Bytes, ethereum } from '@graphprotocol/graph-ts';
import { Address, BigInt, Bytes } from '@graphprotocol/graph-ts';
import { newMockEvent } from 'matchstick-as/assembly/index';
import { App, Dataset, Voucher, VoucherType, Workerpool } from '../../../generated/schema';
import {
AccountAuthorized,
AccountUnauthorized,
} from '../../../generated/templates/Voucher/Voucher';
import {
EligibleAssetAdded,
EligibleAssetRemoved,
RoleGranted,
RoleRevoked,
VoucherCreated,
VoucherTypeCreated,
VoucherTypeDescriptionUpdated,
VoucherTypeDurationUpdated,
} from '../../../generated/VoucherHub/VoucherHub';
import { App, Dataset, Voucher, VoucherType, Workerpool } from '../../../generated/schema';
import {
AccountAuthorized,
AccountUnauthorized,
} from '../../../generated/templates/Voucher/Voucher';
import { EventParamBuilder } from './EventParamBuilder';

export function createVoucherCreatedEvent(
voucher: Address,
Expand All @@ -34,17 +36,13 @@ export function createVoucherCreatedEvent(
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('voucher', ethereum.Value.fromAddress(voucher)));
event.parameters.push(new ethereum.EventParam('owner', ethereum.Value.fromAddress(owner)));
event.parameters.push(
new ethereum.EventParam('voucherType', ethereum.Value.fromUnsignedBigInt(voucherType)),
);
event.parameters.push(
new ethereum.EventParam('expiration', ethereum.Value.fromUnsignedBigInt(expiration)),
);
event.parameters.push(
new ethereum.EventParam('value', ethereum.Value.fromUnsignedBigInt(value)),
);
EventParamBuilder.init()
.address('voucher', voucher)
.address('owner', owner)
.bigInt('voucherType', voucherType)
.bigInt('expiration', expiration)
.bigInt('value', value)
.build(event);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
EventParamBuilder.init()
.address('voucher', voucher)
.address('owner', owner)
.bigInt('voucherType', voucherType)
.bigInt('expiration', expiration)
.bigInt('value', value)
.build(event);
event.parameters = EventParamBuilder.init()
.address('voucher', voucher)
.address('owner', owner)
.bigInt('voucherType', voucherType)
.bigInt('expiration', expiration)
.bigInt('value', value)
.build();

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated here


return event;
}
Expand All @@ -62,8 +60,33 @@ export function createEligibleAssetRemovedEvent(id: BigInt, asset: Address): Eli
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('id', ethereum.Value.fromUnsignedBigInt(id)));
event.parameters.push(new ethereum.EventParam('asset', ethereum.Value.fromAddress(asset)));
EventParamBuilder.init().bigInt('id', id).address('asset', asset).build(event);

return event;
}

export function createVoucherTypeCreatedEvent(
id: BigInt,
description: string,
duration: BigInt,
): VoucherTypeCreated {
let mockEvent = newMockEvent();
let event = new VoucherTypeCreated(
mockEvent.address,
mockEvent.logIndex,
mockEvent.transactionLogIndex,
mockEvent.logType,
mockEvent.block,
mockEvent.transaction,
new Array(),
mockEvent.receipt,
);

EventParamBuilder.init()
.bigInt('id', id)
.string('description', description)
.bigInt('duration', duration)
.build(event);

return event;
}
Expand All @@ -81,8 +104,7 @@ export function createEligibleAssetAddedEvent(id: BigInt, asset: Address): Eligi
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('id', ethereum.Value.fromUnsignedBigInt(id)));
event.parameters.push(new ethereum.EventParam('asset', ethereum.Value.fromAddress(asset)));
EventParamBuilder.init().bigInt('id', id).address('asset', asset).build(event);

return event;
}
Expand All @@ -103,10 +125,7 @@ export function createVoucherTypeDurationUpdatedEvent(
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('id', ethereum.Value.fromUnsignedBigInt(id)));
event.parameters.push(
new ethereum.EventParam('duration', ethereum.Value.fromUnsignedBigInt(duration)),
);
EventParamBuilder.init().bigInt('id', id).bigInt('duration', duration).build(event);

return event;
}
Expand All @@ -127,10 +146,7 @@ export function createVoucherTypeDescriptionUpdatedEvent(
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('id', ethereum.Value.fromUnsignedBigInt(id)));
event.parameters.push(
new ethereum.EventParam('description', ethereum.Value.fromString(description)),
);
EventParamBuilder.init().bigInt('id', id).string('description', description).build(event);

return event;
}
Expand All @@ -148,8 +164,7 @@ export function createRoleGrantedEvent(account: Address, role: Bytes): RoleGrant
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('role', ethereum.Value.fromBytes(role)));
event.parameters.push(new ethereum.EventParam('account', ethereum.Value.fromAddress(account)));
EventParamBuilder.init().bytes('role', role).address('account', account).build(event);

return event;
}
Expand All @@ -167,8 +182,7 @@ export function createRoleRevokedEvent(account: Address, role: Bytes): RoleRevok
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('role', ethereum.Value.fromBytes(role)));
event.parameters.push(new ethereum.EventParam('account', ethereum.Value.fromAddress(account)));
EventParamBuilder.init().bytes('role', role).address('account', account).build(event);

return event;
}
Expand All @@ -189,7 +203,7 @@ export function createAccountAuthorizedEvent(
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('account', ethereum.Value.fromAddress(account)));
EventParamBuilder.init().address('account', account).build(event);

return event;
}
Expand All @@ -210,7 +224,7 @@ export function createAccountUnauthorizedEvent(
mockEvent.receipt,
);

event.parameters.push(new ethereum.EventParam('account', ethereum.Value.fromAddress(account)));
EventParamBuilder.init().address('account', account).build(event);

return event;
}
Expand Down
50 changes: 50 additions & 0 deletions tests/unit/voucherhub/VoucherTypeCreated.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { BigInt } from '@graphprotocol/graph-ts';
import { assert, beforeEach, clearStore, describe, test } from 'matchstick-as/assembly/index';
import { handleVoucherTypeCreated } from '../../../src/voucherHub';
import { createVoucherTypeCreatedEvent } from '../utils/utils';

describe('VoucherTypeCreatedEvent', () => {
beforeEach(() => {
clearStore();
});

test('Should create a new VoucherType entity when an event is emitted', () => {
// Prepare mock event data
let id = BigInt.fromI32(1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add logic in subgraph sources which checks that:
id == voucherEntities.count()
?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That could be an idea
I'm looking on how to do it
But now I'm thinking on how does the subgraph index this ? if it goes from latest block to first block => type 2 is created before type 1 etc. So count won't work (?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo it's chronological (from 0 or start block). From a random non official blog post

The Graph Node will identify the data sources, startBlock from which the data source starts indexing, the contract address, and the events or calls to match. Next, Graph Node will start from the oldest start block (0 if no data source has a startBlock set) and go block by block trying to match any data source specified

I didn't check the official doc though :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some reflexion I have made this
If it's not good for you I can remove this

let description = 'Test Voucher';
let duration = BigInt.fromI32(30);

// Create mock event
let event = createVoucherTypeCreatedEvent(id, description, duration);

handleVoucherTypeCreated(event);

let entityId = id.toString();
assert.entityCount('VoucherType', 1);
assert.fieldEquals('VoucherType', entityId, 'id', entityId);
assert.fieldEquals('VoucherType', entityId, 'description', description);
assert.fieldEquals('VoucherType', entityId, 'duration', duration.toString());
assert.fieldEquals('VoucherType', entityId, 'eligibleAssets', '[]');
});

test('Should not create 2 VoucherType entities with the same voucher type id', () => {
let id = BigInt.fromI32(1);
let description1 = 'Test Voucher';
let duration1 = BigInt.fromI32(30);
let event1 = createVoucherTypeCreatedEvent(id, description1, duration1);
handleVoucherTypeCreated(event1);

let description2 = 'Test Voucher number 2';
let duration2 = BigInt.fromI32(150);
let event2 = createVoucherTypeCreatedEvent(id, description2, duration2);
handleVoucherTypeCreated(event2);

// Assert that no entity was created
assert.entityCount('VoucherType', 1);
let entityId = id.toString();
assert.fieldEquals('VoucherType', entityId, 'id', entityId);
assert.fieldEquals('VoucherType', entityId, 'description', description1);
assert.fieldEquals('VoucherType', entityId, 'duration', duration1.toString());
assert.fieldEquals('VoucherType', entityId, 'eligibleAssets', '[]');
});
});