-
Notifications
You must be signed in to change notification settings - Fork 97
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improved assertion options for agent errors (#908)
* feat: improved assertion options for agent errors * fixes observable test * chore: cast to v2ResponseBody * chore: build fix * chore: prettier --------- Co-authored-by: Jason I <jason.ibrahim@dfinity.org>
- Loading branch information
Showing
7 changed files
with
180 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* eslint-disable no-prototype-builtins */ | ||
import { QueryResponseStatus, SubmitResponse } from './agent'; | ||
import { | ||
ActorCallError, | ||
AgentError, | ||
QueryCallRejectedError, | ||
UpdateCallRejectedError, | ||
} from './errors'; | ||
import { RequestId } from './request_id'; | ||
|
||
test('AgentError', () => { | ||
const error = new AgentError('message'); | ||
expect(error.message).toBe('message'); | ||
expect(error.name).toBe('AgentError'); | ||
expect(error instanceof Error).toBe(true); | ||
expect(error instanceof AgentError).toBe(true); | ||
expect(error instanceof ActorCallError).toBe(false); | ||
expect(AgentError.prototype.isPrototypeOf(error)).toBe(true); | ||
}); | ||
|
||
test('ActorCallError', () => { | ||
const error = new ActorCallError('rrkah-fqaaa-aaaaa-aaaaq-cai', 'methodName', 'query', { | ||
props: 'props', | ||
}); | ||
expect(error.message).toBe(`Call failed: | ||
Canister: rrkah-fqaaa-aaaaa-aaaaq-cai | ||
Method: methodName (query) | ||
"props": "props"`); | ||
expect(error.name).toBe('ActorCallError'); | ||
expect(error instanceof Error).toBe(true); | ||
expect(error instanceof AgentError).toBe(true); | ||
expect(error instanceof ActorCallError).toBe(true); | ||
expect(ActorCallError.prototype.isPrototypeOf(error)).toBe(true); | ||
}); | ||
|
||
test('QueryCallRejectedError', () => { | ||
const error = new QueryCallRejectedError('rrkah-fqaaa-aaaaa-aaaaq-cai', 'methodName', { | ||
status: QueryResponseStatus.Rejected, | ||
reject_code: 1, | ||
reject_message: 'reject_message', | ||
error_code: 'error_code', | ||
}); | ||
expect(error.message).toBe(`Call failed: | ||
Canister: rrkah-fqaaa-aaaaa-aaaaq-cai | ||
Method: methodName (query) | ||
"Status": "rejected" | ||
"Code": "SysFatal" | ||
"Message": "reject_message"`); | ||
expect(error.name).toBe('QueryCallRejectedError'); | ||
expect(error instanceof Error).toBe(true); | ||
expect(error instanceof AgentError).toBe(true); | ||
expect(error instanceof ActorCallError).toBe(true); | ||
expect(error instanceof QueryCallRejectedError).toBe(true); | ||
expect(QueryCallRejectedError.prototype.isPrototypeOf(error)).toBe(true); | ||
}); | ||
|
||
test('UpdateCallRejectedError', () => { | ||
const response: SubmitResponse['response'] = { | ||
ok: false, | ||
status: 400, | ||
statusText: 'rejected', | ||
body: { | ||
error_code: 'error_code', | ||
reject_code: 1, | ||
reject_message: 'reject_message', | ||
}, | ||
headers: [], | ||
}; | ||
const error = new UpdateCallRejectedError( | ||
'rrkah-fqaaa-aaaaa-aaaaq-cai', | ||
'methodName', | ||
new ArrayBuffer(1) as RequestId, | ||
response, | ||
); | ||
expect(error.message).toBe(`Call failed: | ||
Canister: rrkah-fqaaa-aaaaa-aaaaq-cai | ||
Method: methodName (update) | ||
"Request ID": "00" | ||
"Error code": "error_code" | ||
"Reject code": "1" | ||
"Reject message": "reject_message"`); | ||
expect(error.name).toBe('UpdateCallRejectedError'); | ||
expect(error instanceof Error).toBe(true); | ||
expect(error instanceof AgentError).toBe(true); | ||
expect(error instanceof ActorCallError).toBe(true); | ||
expect(error instanceof UpdateCallRejectedError).toBe(true); | ||
expect(UpdateCallRejectedError.prototype.isPrototypeOf(error)).toBe(true); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,94 @@ | ||
import { Principal } from '@dfinity/principal'; | ||
import { | ||
QueryResponseRejected, | ||
ReplicaRejectCode, | ||
SubmitResponse, | ||
v2ResponseBody, | ||
} from './agent/api'; | ||
import { RequestId } from './request_id'; | ||
import { toHex } from './utils/buffer'; | ||
|
||
/** | ||
* An error that happens in the Agent. This is the root of all errors and should be used | ||
* everywhere in the Agent code (this package). | ||
* | ||
* @todo https://github.com/dfinity/agent-js/issues/420 | ||
*/ | ||
export class AgentError extends Error { | ||
public name = 'AgentError'; | ||
public __proto__ = AgentError.prototype; | ||
constructor(public readonly message: string) { | ||
super(message); | ||
Object.setPrototypeOf(this, AgentError.prototype); | ||
} | ||
} | ||
|
||
export class ActorCallError extends AgentError { | ||
public name = 'ActorCallError'; | ||
public __proto__ = ActorCallError.prototype; | ||
constructor( | ||
public readonly canisterId: Principal | string, | ||
public readonly methodName: string, | ||
public readonly type: 'query' | 'update', | ||
public readonly props: Record<string, string>, | ||
) { | ||
const cid = Principal.from(canisterId); | ||
super( | ||
[ | ||
`Call failed:`, | ||
` Canister: ${cid.toText()}`, | ||
` Method: ${methodName} (${type})`, | ||
...Object.getOwnPropertyNames(props).map(n => ` "${n}": ${JSON.stringify(props[n])}`), | ||
].join('\n'), | ||
); | ||
Object.setPrototypeOf(this, ActorCallError.prototype); | ||
} | ||
} | ||
|
||
export class QueryCallRejectedError extends ActorCallError { | ||
public name = 'QueryCallRejectedError'; | ||
public __proto__ = QueryCallRejectedError.prototype; | ||
constructor( | ||
canisterId: Principal | string, | ||
methodName: string, | ||
public readonly result: QueryResponseRejected, | ||
) { | ||
const cid = Principal.from(canisterId); | ||
super(cid, methodName, 'query', { | ||
Status: result.status, | ||
Code: ReplicaRejectCode[result.reject_code] ?? `Unknown Code "${result.reject_code}"`, | ||
Message: result.reject_message, | ||
}); | ||
Object.setPrototypeOf(this, QueryCallRejectedError.prototype); | ||
} | ||
} | ||
|
||
export class UpdateCallRejectedError extends ActorCallError { | ||
public name = 'UpdateCallRejectedError'; | ||
public __proto__ = UpdateCallRejectedError.prototype; | ||
constructor( | ||
canisterId: Principal | string, | ||
methodName: string, | ||
public readonly requestId: RequestId, | ||
public readonly response: SubmitResponse['response'], | ||
) { | ||
const cid = Principal.from(canisterId); | ||
super(cid, methodName, 'update', { | ||
'Request ID': toHex(requestId), | ||
...(response.body | ||
? { | ||
...((response.body as v2ResponseBody).error_code | ||
? { | ||
'Error code': (response.body as v2ResponseBody).error_code, | ||
} | ||
: {}), | ||
'Reject code': String((response.body as v2ResponseBody).reject_code), | ||
'Reject message': (response.body as v2ResponseBody).reject_message, | ||
} | ||
: { | ||
'HTTP status code': response.status.toString(), | ||
'HTTP status text': response.statusText, | ||
}), | ||
}); | ||
Object.setPrototypeOf(this, UpdateCallRejectedError.prototype); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters