Skip to content

Commit

Permalink
Merge pull request #1444 from demergent-labs/service_property_test
Browse files Browse the repository at this point in the history
Service property test
  • Loading branch information
lastmjs authored Nov 8, 2023
2 parents e419f88 + 435c1fb commit 777b421
Show file tree
Hide file tree
Showing 8 changed files with 1,277 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ jobs:
"property_tests/tests/null",
"property_tests/tests/principal",
"property_tests/tests/record",
"property_tests/tests/service",
"property_tests/tests/text",
"property_tests/tests/tuple",
"property_tests/tests/variant",
Expand Down
112 changes: 112 additions & 0 deletions property_tests/arbitraries/candid/reference/service_arb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import fc from 'fast-check';
import { Principal } from '@dfinity/principal';

import { PrincipalArb } from './principal_arb';
import { VoidArb } from '../primitive/void';
import { CandidMeta } from '../candid_arb';
import { CandidTypeArb } from '../candid_type_arb';
import { UniqueIdentifierArb } from '../../unique_identifier_arb';
import { JsFunctionNameArb } from '../../js_function_name_arb';

// TODO:
// - services that are more than type-definitions, i.e. have functionality
// - async service methods
// - non-query methods
// - actually using the service

// Example Service:
// const SomeService = Canister({
// method1: query([], Void),
// method2: query([text, text, nat64], nat64),
// });

type ServiceMethod = {
name: string;
imports: Set<string>;
typeDeclarations: string[];
src: string;
};

const ServiceMethodArb = fc
.tuple(
JsFunctionNameArb,
fc.constantFrom('query', 'update'),
fc.array(CandidTypeArb),
fc.oneof(CandidTypeArb, VoidArb)
)
.map(([name, mode, params, returnType]): ServiceMethod => {
const paramThings = params.map((param) => param.src.candidType);

const typeDeclarations = params.reduce(
(acc, { src: { typeDeclaration } }) => {
return typeDeclaration ? [...acc, typeDeclaration] : acc;
},
returnType.src.typeDeclaration
? [returnType.src.typeDeclaration]
: new Array<string>()
);

const src = `${name}: ${mode}([${paramThings}], ${returnType.src.candidType})`;

const imports = params.reduce(
(acc, param) => {
return new Set([...acc, ...param.src.imports]);
},
new Set([mode, ...returnType.src.imports])
);

return {
name,
imports,
typeDeclarations,
src
};
});

export const ServiceArb = fc
.tuple(
UniqueIdentifierArb('typeDeclaration'),
fc.array(ServiceMethodArb),
PrincipalArb
)
.map(([name, serviceMethods, principal]): CandidMeta<Principal> => {
const imports = new Set([
...serviceMethods.flatMap((method) => [...method.imports]),
'Canister',
'query'
]);

const typeDeclaration = generateTypeDeclaration(name, serviceMethods);

const typeDeclarationAndChildren = [
...serviceMethods.flatMap((method) => method.typeDeclarations),
typeDeclaration
].join('\n');

const valueLiteral = `${name}(${principal.src.valueLiteral})`;

const value = principal.value;

return {
src: {
candidType: name,
typeDeclaration: typeDeclarationAndChildren,
imports,
valueLiteral
},
value
};
});

function generateTypeDeclaration(
name: string,
serviceMethods: ServiceMethod[]
): string {
const methods = serviceMethods
.map((serviceMethod) => serviceMethod.src)
.filter((typeDeclaration) => typeDeclaration)
.join(',\n');

// TODO: Is this going to work if serviceMethods.length === 0?
return `const ${name} = Canister({${methods}});`;
}
13 changes: 8 additions & 5 deletions property_tests/arbitraries/canister_arb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,22 @@ export function CanisterArb(testArb: fc.Arbitrary<TestSample>) {

const imports = [
...new Set(
queryMethods.reduce((acc, queryMethod) => {
return [...acc, ...queryMethod.imports];
}, [] as string[])
queryMethods.reduce(
(acc, queryMethod) => {
return [...acc, ...queryMethod.imports];
},
['Canister', 'query']
)
)
];
].join();

const tests: Test[] = queryMethods.map(
(queryMethod) => queryMethod.test
);

return {
sourceCode: `
import { Canister, query, ${imports.join(', ')} } from 'azle';
import { ${imports} } from 'azle';
import { deepEqual } from 'fast-equals';
// TODO solve the underlying principal problem https://github.com/demergent-labs/azle/issues/1443
import { Principal as DfinityPrincipal } from '@dfinity/principal';
Expand Down
16 changes: 16 additions & 0 deletions property_tests/tests/service/dfx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"canisters": {
"canister": {
"type": "custom",
"main": "src/index.ts",
"candid": "src/index.did",
"build": "npx azle canister",
"wasm": ".azle/canister/canister.wasm",
"gzip": true,
"declarations": {
"output": "test/dfx_generated/canister",
"node_compatibility": true
}
}
}
}
Loading

0 comments on commit 777b421

Please sign in to comment.