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

Test: Add HTTP request update property tests #1533

Merged
merged 25 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ jobs:
"examples/web_assembly",
"property_tests/tests/blob",
"property_tests/tests/bool",
"property_tests/tests/canister_methods/http_request",
"property_tests/tests/canister_methods/http_request_update",
"property_tests/tests/canister_methods/init",
"property_tests/tests/canister_methods/post_upgrade",
"property_tests/tests/canister_methods/pre_upgrade",
Expand Down
4 changes: 0 additions & 4 deletions examples/motoko_examples/http_counter/dfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
"build": "npx azle http_counter",
"wasm": ".azle/http_counter/http_counter.wasm",
"gzip": true,
"declarations": {
"output": "test/dfx_generated/http_counter",
"node_compatibility": true
},
"metadata": [
{
"name": "candid:service",
Expand Down
4 changes: 2 additions & 2 deletions examples/motoko_examples/http_counter/src/index.did
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
service: () -> {
http_request: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:record {arbitrary_data:text}; callback:func (text) -> (record {token:opt record {arbitrary_data:text}; body:vec nat8}) query}}; status_code:nat16}) query;
http_request_update: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:record {arbitrary_data:text}; callback:func (text) -> (record {token:opt record {arbitrary_data:text}; body:vec nat8}) query}}; status_code:nat16});
http_request: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:record {arbitrary_data:text}; callback:func (record {arbitrary_data:text}) -> (opt record {token:opt record {arbitrary_data:text}; body:vec nat8}) query}}; status_code:nat16}) query;
http_request_update: (record {url:text; method:text; body:vec nat8; headers:vec record {text; text}; certificate_version:opt nat16}) -> (record {body:vec nat8; headers:vec record {text; text}; upgrade:opt bool; streaming_strategy:opt variant {Callback:record {token:record {arbitrary_data:text}; callback:func (record {arbitrary_data:text}) -> (opt record {token:opt record {arbitrary_data:text}; body:vec nat8}) query}}; status_code:nat16});
http_streaming: (record {arbitrary_data:text}) -> (record {token:opt record {arbitrary_data:text}; body:vec nat8}) query;
}
115 changes: 39 additions & 76 deletions examples/motoko_examples/http_counter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,74 +1,34 @@
import {
blob,
bool,
Canister,
Func,
HeaderField,
HttpRequest,
HttpResponse,
ic,
init,
nat,
nat16,
None,
Opt,
query,
Record,
Some,
StableBTreeMap,
StreamingCallbackHttpResponse,
text,
Tuple,
update,
Variant,
Vec
update
} from 'azle';

const Token = Record({
// add whatever fields you'd like
arbitrary_data: text
});

const StreamingCallbackHttpResponse = Record({
body: blob,
token: Opt(Token)
});

export const Callback = Func([text], StreamingCallbackHttpResponse, 'query');

const CallbackStrategy = Record({
callback: Callback,
token: Token
});

const StreamingStrategy = Variant({
Callback: CallbackStrategy
});

type HeaderField = [text, text];
const HeaderField = Tuple(text, text);

const HttpResponse = Record({
status_code: nat16,
headers: Vec(HeaderField),
body: blob,
streaming_strategy: Opt(StreamingStrategy),
upgrade: Opt(bool)
});

const HttpRequest = Record({
method: text,
url: text,
headers: Vec(HeaderField),
body: blob,
certificate_version: Opt(nat16)
});

let stableStorage = StableBTreeMap<text, nat>(0);

export default Canister({
init: init([], () => {
stableStorage.insert('counter', 0n);
}),
http_request: query([HttpRequest], HttpResponse, (req) => {
console.log('Hello from http_request');

http_request: query([HttpRequest], HttpResponse(Token), (req) => {
if (req.method === 'GET') {
if (req.headers.find(isGzip) === undefined) {
if (req.url === '/stream') {
Expand Down Expand Up @@ -141,7 +101,7 @@ export default Canister({
upgrade: None
};
}),
http_request_update: update([HttpRequest], HttpResponse, (req) => {
http_request_update: update([HttpRequest], HttpResponse(Token), (req) => {
if (req.method === 'POST') {
const counterOpt = stableStorage.get('counter');
const counter =
Expand Down Expand Up @@ -195,38 +155,41 @@ export default Canister({
upgrade: None
};
}),
http_streaming: query([Token], StreamingCallbackHttpResponse, (token) => {
console.log('Hello from http_streaming');
switch (token.arbitrary_data) {
case 'start': {
return {
body: encode(' is '),
token: Some({ arbitrary_data: 'next' })
};
}
case 'next': {
const counterOpt = stableStorage.get('counter');
const counter =
'None' in counterOpt
? ic.trap('counter does not exist')
: counterOpt.Some;
http_streaming: query(
[Token],
StreamingCallbackHttpResponse(Token),
(token) => {
switch (token.arbitrary_data) {
case 'start': {
return {
body: encode(' is '),
token: Some({ arbitrary_data: 'next' })
};
}
case 'next': {
const counterOpt = stableStorage.get('counter');
const counter =
'None' in counterOpt
? ic.trap('counter does not exist')
: counterOpt.Some;

return {
body: encode(`${counter}`),
token: Some({ arbitrary_data: 'last' })
};
}
case 'last': {
return {
body: encode(' streaming\n'),
token: None
};
}
default: {
return ic.trap('unreachable');
return {
body: encode(`${counter}`),
token: Some({ arbitrary_data: 'last' })
};
}
case 'last': {
return {
body: encode(' streaming\n'),
token: None
};
}
default: {
return ic.trap('unreachable');
}
}
}
})
)
});

function isGzip(x: HeaderField): boolean {
Expand Down
4 changes: 0 additions & 4 deletions examples/motoko_examples/http_counter/test/pretest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ async function pretest() {
execSync(`dfx deploy http_counter`, {
stdio: 'inherit'
});

execSync(`dfx generate http_counter`, {
stdio: 'inherit'
});
}

pretest();
91 changes: 41 additions & 50 deletions examples/motoko_examples/http_counter/test/tests.ts
Original file line number Diff line number Diff line change
@@ -1,79 +1,75 @@
import { Test } from 'azle/test';
import { execSync } from 'child_process';
import * as dns from 'node:dns';
dns.setDefaultResultOrder('ipv4first');

const skip = true;

export function getTests(): Test[] {
return [
{
name: 'init get count',
skip,
test: async () => {
return {
Ok: getCount() === getExpectedGetCountResult(0)
Ok: (await getCount()) === getExpectedGetCountResult(0)
};
}
},
{
name: 'first increment',
skip,
test: async () => {
return {
Ok: count() === getExpectedCountResult(1)
Ok: (await count()) === getExpectedCountResult(1)
};
}
},
{
name: 'second increment',
skip,
test: async () => {
return {
Ok: count() === getExpectedCountResult(2)
Ok: (await count()) === getExpectedCountResult(2)
};
}
},
{
name: 'get count',
skip,
test: async () => {
return {
Ok: getCount() === getExpectedGetCountResult(2)
Ok: (await getCount()) === getExpectedGetCountResult(2)
};
}
},
{
name: 'gzipped increment',
skip,
test: async () => {
return {
Ok: countGzip() === 'update'
Ok: (await countGzip()) === 'update'
};
}
},
{
name: 'get gzipped count',
skip,
test: async () => {
return {
Ok: getCountGzip() === 'query'
Ok: (await getCountGzip()) === 'query'
};
}
},
{
name: 'get streaming count',
skip,
test: async () => {
return {
Ok: getCountStream() === getExpectedGetCountStreamResult(3)
Ok:
(await getCountStream()) ===
getExpectedGetCountStreamResult(3)
};
}
},
{
name: 'final get count',
skip,
test: async () => {
return {
Ok: getCount() === getExpectedGetCountResult(3)
Ok: (await getCount()) === getExpectedGetCountResult(3)
};
}
}
Expand All @@ -84,49 +80,44 @@ function getCanisterID(): string {
return execSync(`dfx canister id http_counter`).toString().trim();
}

function count(): string {
function getUrl(): string {
const canister_id = getCanisterID();
return execSync(
`curl --silent -X POST "${canister_id}.localhost:8000/" --resolve "${canister_id}.localhost:8000:127.0.0.1"`
)
.toString()
.trim();
return `http://${canister_id}.localhost:8000/`;
}

function countGzip(): string {
const canister_id = getCanisterID();
return execSync(
`curl --compressed --silent -X POST "${canister_id}.localhost:8000/" --resolve "${canister_id}.localhost:8000:127.0.0.1"`
)
.toString()
.trim();
async function count(): Promise<string> {
const response = await fetch(getUrl(), {
method: 'POST',
headers: [['accept-encoding', '']]
});
return (await response.text()).trim();
}

function getCount(): string {
const canister_id = getCanisterID();
return execSync(
`curl --silent "${canister_id}.localhost:8000/" --resolve "${canister_id}.localhost:8000:127.0.0.1"`
)
.toString()
.trim();
async function countGzip(): Promise<string> {
const response = await fetch(getUrl(), {
method: 'POST'
});
return (await response.text()).trim();
}

function getCountStream(): string {
const canister_id = getCanisterID();
return execSync(
`curl --silent "${canister_id}.localhost:8000/stream" --resolve "${canister_id}.localhost:8000:127.0.0.1"`
)
.toString()
.trim();
async function getCount(): Promise<string> {
const response = await fetch(getUrl(), {
headers: [['accept-encoding', '']]
});
return (await response.text()).trim();
}

function getCountGzip(): string {
const canister_id = getCanisterID();
return execSync(
`curl --compressed --silent "${canister_id}.localhost:8000/" --resolve "${canister_id}.localhost:8000:127.0.0.1"`
)
.toString()
.trim();
async function getCountStream(): Promise<string> {
const streamUrl = `${getUrl()}stream`;
const response = await fetch(streamUrl, {
headers: [['accept-encoding', '']]
});
return (await response.text()).trim();
}

async function getCountGzip(): Promise<string> {
const response = await fetch(getUrl());
return (await response.text()).trim();
}

function getExpectedGetCountResult(expectedCount: number): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function QueryMethodArb<
ReturnTypeAgentResponseValue
>;
callbackLocation?: CallbackLocation;
name?: string;
}
) {
return fc
Expand All @@ -66,14 +67,15 @@ export function QueryMethodArb<
)
.map(
([
functionName,
defaultFunctionName,
paramTypes,
returnType,
defaultCallbackLocation,
callbackName
]): QueryMethod => {
const callbackLocation =
constraints.callbackLocation ?? defaultCallbackLocation;
const functionName = constraints.name ?? defaultFunctionName;

const imports = new Set([
'query',
Expand Down
Loading
Loading