Skip to content

Commit

Permalink
tests: refactor file data source tests and add new tests
Browse files Browse the repository at this point in the history
  • Loading branch information
incrypto32 committed Jul 9, 2024
1 parent 06bf21b commit 375c423
Show file tree
Hide file tree
Showing 6 changed files with 349 additions and 317 deletions.
6 changes: 6 additions & 0 deletions tests/runner-tests/file-data-sources/abis/Contract.abi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
"internalType": "string",
"name": "testCommand",
"type": "string"
},
{
"indexed": false,
"internalType": "string",
"name": "data",
"type": "string"
}
],
"name": "TestEvent",
Expand Down
13 changes: 4 additions & 9 deletions tests/runner-tests/file-data-sources/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
type IpfsFile @entity {
type FileEntity @entity {
id: ID!
content: String!
foo: Foo @relation
}

type IpfsFile1 @entity {
type Foo @entity {
id: ID!
content: String!
ipfs: FileEntity @derivedFrom(field: "foo")
}

type SpawnTestEntity @entity {
id: ID!
content: String!
context: String!
}
226 changes: 122 additions & 104 deletions tests/runner-tests/file-data-sources/src/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,130 +4,148 @@ import {
BigInt,
Bytes,
DataSourceContext,
store,
log,
} from "@graphprotocol/graph-ts";
import { TestEvent } from "../generated/Contract/Contract";
import { IpfsFile, IpfsFile1, SpawnTestEntity } from "../generated/schema";

// CID of `file-data-sources/abis/Contract.abi` after being processed by graph-cli.
const KNOWN_CID = "QmQ2REmceVtzawp7yrnxLQXgNNCtFHEnig6fL9aqE1kcWq";

export function handleBlock(block: ethereum.Block): void {
let entity = new IpfsFile("onchain");
entity.content = "onchain";
entity.save();

// This will create the same data source twice, once at block 0 and another at block 2.
// The creation at block 2 should be detected as a duplicate and therefore a noop.
if (block.number == BigInt.fromI32(0) || block.number == BigInt.fromI32(2)) {
dataSource.create("File", [KNOWN_CID]);
import { FileEntity, Foo } from "../generated/schema";

const ONCHAIN_FROM_OFFCHAIN = "CREATE_ONCHAIN_DATASOURCE_FROM_OFFCHAIN_HANDLER";
const CREATE_FILE = "CREATE_FILE";
// const CREATE_FILE_FROM_HANDLE_FILE = "CREATE_FILE_FROM_HANDLE_FILE";
const CREATE_UNDEFINED_ENTITY = "CREATE_UNDEFINED_ENTITY";
const CREATE_CONFLICTING_ENTITY = "CREATE_CONFLICTING_ENTITY";
const SPAWN_FDS_FROM_OFFCHAIN_HANDLER = "SPAWN_FDS_FROM_OFFCHAIN_HANDLER";
const ACCESS_AND_UPDATE_OFFCHAIN_ENTITY_IN_ONCHAIN_HANDLER =
"ACCESS_AND_UPDATE_OFFCHAIN_ENTITY_IN_ONCHAIN_HANDLER";
const ACCESS_FILE_ENTITY_THROUGH_DERIVED_FIELD =
"ACCESS_FILE_ENTITY_THROUGH_DERIVED_FIELD";

const CREATE_FOO = "CREATE_FOO";
export function handleTestEvent(event: TestEvent): void {
if (event.params.testCommand == CREATE_FILE) {
dataSource.createWithContext(
"File",
[event.params.data],
new DataSourceContext(),
);
}

if (block.number == BigInt.fromI32(1)) {
let entity = IpfsFile.load("onchain")!;
assert(entity.content == "onchain");

// The test assumes file data sources are processed in the block in which they are created.
// So the ds created at block 0 will have been processed.
//
// Test that onchain data sources cannot read offchain data.
assert(IpfsFile.load(KNOWN_CID) == null);
if (event.params.testCommand == SPAWN_FDS_FROM_OFFCHAIN_HANDLER) {
let comma_separated_hash = event.params.data;
let hash1 = comma_separated_hash.split(",")[0];
let hash2 = comma_separated_hash.split(",")[1];
let context = new DataSourceContext();
context.setString("command", SPAWN_FDS_FROM_OFFCHAIN_HANDLER);
context.setString("hash", hash2);

// Test that using an invalid CID will be ignored
dataSource.create("File", ["hi, I'm not valid"]);
log.info(
"Creating file data source from handleFile, command : {} ,hash1: {}, hash2: {}",
[SPAWN_FDS_FROM_OFFCHAIN_HANDLER, hash1, hash2],
);
dataSource.createWithContext("File", [hash1], context);
}

// This will invoke File1 data source with same CID, which will be used
// to test whether same cid is triggered across different data source.
if (block.number == BigInt.fromI32(3)) {
// Test that onchain data sources cannot read offchain data (again, but this time more likely to hit the DB than the write queue).
assert(IpfsFile.load(KNOWN_CID) == null);

dataSource.create("File1", [KNOWN_CID]);
if (event.params.testCommand == ONCHAIN_FROM_OFFCHAIN) {
let context = new DataSourceContext();
context.setString("command", ONCHAIN_FROM_OFFCHAIN);
context.setString("address", "0x0000000000000000000000000000000000000000");
dataSource.createWithContext("File", [event.params.data], context);
}
}

export function handleTestEvent(event: TestEvent): void {
let command = event.params.testCommand;

if (command == "createFile2") {
// Will fail the subgraph when processed due to mismatch in the entity type and 'entities'.
dataSource.create("File2", [KNOWN_CID]);
} else if (command == "saveConflictingEntity") {
// Will fail the subgraph because the same entity has been created in a file data source.
let entity = new IpfsFile(KNOWN_CID);
entity.content = "empty";
entity.save();
} else if (command == "createFile1") {
// Will fail the subgraph with a conflict between two entities created by offchain data sources.
let context = new DataSourceContext();
context.setBytes("hash", event.block.hash);
dataSource.createWithContext("File1", [KNOWN_CID], context);
} else if (command == "spawnOffChainHandlerTest") {
// Used to test the spawning of a file data source from another file data source handler.
// `SpawnTestHandler` will spawn a file data source that will be handled by `spawnOffChainHandlerTest`,
// which creates another file data source `OffChainDataSource`, which will be handled by `handleSpawnedTest`.
if (event.params.testCommand == CREATE_UNDEFINED_ENTITY) {
log.info("Creating undefined entity", []);
let context = new DataSourceContext();
context.setString("command", command);
dataSource.createWithContext("SpawnTestHandler", [KNOWN_CID], context);
} else if (command == "spawnOnChainHandlerTest") {
// Used to test the failure of spawning of on-chain data source from a file data source handler.
// `SpawnTestHandler` will spawn a file data source that will be handled by `spawnTestHandler`,
// which creates an `OnChainDataSource`, which should fail since spawning onchain datasources
// from offchain handlers is not allowed.
let context = new DataSourceContext();
context.setString("command", command);
dataSource.createWithContext("SpawnTestHandler", [KNOWN_CID], context);
} else {
assert(false, "Unknown command: " + command);
context.setString("command", CREATE_UNDEFINED_ENTITY);
dataSource.createWithContext("File", [event.params.data], context);
}
}

export function handleFile(data: Bytes): void {
// Test that offchain data sources cannot read onchain data.
assert(IpfsFile.load("onchain") == null);
if (event.params.testCommand == CREATE_CONFLICTING_ENTITY) {
log.info("Creating conflicting entity", []);
let entity = new FileEntity(event.params.data);
entity.content = "content";
entity.save();
}

if (
dataSource.stringParam() != "QmVkvoPGi9jvvuxsHDVJDgzPEzagBaWSZRYoRDzU244HjZ"
event.params.testCommand ==
ACCESS_AND_UPDATE_OFFCHAIN_ENTITY_IN_ONCHAIN_HANDLER
) {
// Test that an offchain data source cannot read from another offchain data source.
assert(
IpfsFile.load("QmVkvoPGi9jvvuxsHDVJDgzPEzagBaWSZRYoRDzU244HjZ") == null
);
let hash = event.params.data;
log.info("Creating file data source from handleFile: {}", [hash]);
let entity = FileEntity.load(event.params.data);
if (entity == null) {
log.info("Entity not found", []);
} else {
// This should never be logged if the entity was created in the offchain handler
// Such entities are not accessible in onchain handlers and will return null on load
log.info("Updating entity content", []);
entity.content = "updated content";
entity.save();
}
}

let entity = new IpfsFile(dataSource.stringParam());
entity.content = data.toString();
entity.save();

// Test that an offchain data source can load its own entities
let loaded_entity = IpfsFile.load(dataSource.stringParam())!;
assert(loaded_entity.content == entity.content);
}
if (event.params.testCommand == CREATE_FOO) {
let entity = new Foo(event.params.data);
entity.save();
let context = new DataSourceContext();
context.setString("command", CREATE_FOO);
dataSource.createWithContext("File", [event.params.data], context);
}

export function handleFile1(data: Bytes): void {
let entity = new IpfsFile1(dataSource.stringParam());
entity.content = data.toString();
entity.save();
}
if (event.params.testCommand == ACCESS_FILE_ENTITY_THROUGH_DERIVED_FIELD) {
let entity = Foo.load(event.params.data);
if (entity == null) {
log.info("Entity not found", []);
} else {
log.info("Accessing file entity through derived field", []);
let fileEntity = entity.ipfs.load();

// Used to test spawning a file data source from another file data source handler.
// This function spawns a file data source that will be handled by `handleSpawnedTest`.
export function spawnTestHandler(data: Bytes): void {
let context = new DataSourceContext();
context.setString("file", "fromSpawnTestHandler");
let command = dataSource.context().getString("command");
if (command == "spawnOffChainHandlerTest") {
dataSource.createWithContext("OffChainDataSource", [KNOWN_CID], context);
} else if (command == "spawnOnChainHandlerTest") {
dataSource.createWithContext("OnChainDataSource", [KNOWN_CID], context);
assert(fileEntity.length == 0, "Expected exactly one file entity");
}
}
}

// This is the handler for the data source spawned by `spawnOffChainHandlerTest`.
export function handleSpawnedTest(data: Bytes): void {
let entity = new SpawnTestEntity(dataSource.stringParam());
let context = dataSource.context().getString("file");
entity.content = data.toString();
entity.context = context;
entity.save();
export function handleFile(data: Bytes): void {
log.info("handleFile {}", [dataSource.stringParam()]);
let context = dataSource.context();

if (context.isSet("command")) {
let contextCommand = context.getString("command");

if (contextCommand == SPAWN_FDS_FROM_OFFCHAIN_HANDLER) {
let hash = context.getString("hash");
log.info("Creating file data source from handleFile: {}", [hash]);
dataSource.createWithContext("File", [hash], new DataSourceContext());
}

if (contextCommand == ONCHAIN_FROM_OFFCHAIN) {
log.info("Creating onchain data source from offchain handler", []);
let address = context.getString("address");
dataSource.create("OnChainDataSource", [address]);
}

if (contextCommand == CREATE_UNDEFINED_ENTITY) {
log.info("Creating undefined entity", []);
let entity = new Foo(dataSource.stringParam());
entity.save();
}

if (contextCommand == CREATE_FOO) {
log.info("Creating FileEntity with relation to Foo", []);
let entity = new FileEntity(dataSource.stringParam());
entity.foo = dataSource.stringParam();
entity.content = data.toString();
entity.save();
}
} else {
log.info("Creating FileEntity from handleFile: {} , content : {}", [
dataSource.stringParam(),
data.toString(),
]);

let entity = new FileEntity(dataSource.stringParam());
entity.content = data.toString();
entity.save();
}
}
65 changes: 5 additions & 60 deletions tests/runner-tests/file-data-sources/subgraph.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ dataSources:
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Gravatar
- FileEntity
- Foo
abis:
- name: Contract
file: ./abis/Contract.abi
blockHandlers:
- handler: handleBlock
eventHandlers:
- event: TestEvent(string)
- event: TestEvent(string,string)
handler: handleTestEvent
file: ./src/mapping.ts
templates:
Expand All @@ -38,10 +37,8 @@ templates:
abis:
- name: Contract
file: ./abis/Contract.abi
blockHandlers:
- handler: handleBlock
eventHandlers:
- event: TestEvent(string)
- event: TestEvent(string,string)
handler: handleTestEvent
file: ./src/mapping.ts
- kind: file/ipfs
Expand All @@ -51,61 +48,9 @@ templates:
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IpfsFile
- FileEntity
abis:
- name: Contract
file: ./abis/Contract.abi
handler: handleFile
file: ./src/mapping.ts
- kind: file/ipfs
name: File1
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IpfsFile1
abis:
- name: Contract
file: ./abis/Contract.abi
handler: handleFile1
file: ./src/mapping.ts
- kind: file/ipfs
name: File2
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- IpfsFile # will trigger an error, should be IpfsFile1
abis:
- name: Contract
file: ./abis/Contract.abi
handler: handleFile1
file: ./src/mapping.ts
- kind: file/ipfs
name: SpawnTestHandler
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- SpawnTestEntity
abis:
- name: Contract
file: ./abis/Contract.abi
handler: spawnTestHandler
file: ./src/mapping.ts
- kind: file/ipfs
name: OffChainDataSource
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- SpawnTestEntity
abis:
- name: Contract
file: ./abis/Contract.abi
handler: handleSpawnedTest
file: ./src/mapping.ts
Loading

0 comments on commit 375c423

Please sign in to comment.