From 34a800246d6bdde89aba6f5625f7f2d04001d3c9 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Mon, 25 Nov 2024 17:11:24 -0500 Subject: [PATCH 1/5] feat: add dissociateToken tests Signed-off-by: Rob Walworth --- mirrorNodeClient.js | 5 + .../test_tokenDissociateTransaction.js | 468 ++++++++++++++++++ 2 files changed, 473 insertions(+) create mode 100644 test/token-service/test_tokenDissociateTransaction.js diff --git a/mirrorNodeClient.js b/mirrorNodeClient.js index f0f8767..b34619b 100644 --- a/mirrorNodeClient.js +++ b/mirrorNodeClient.js @@ -20,6 +20,11 @@ class MirrorNodeClient { const url = `${this.mirrorNodeRestUrl}/api/v1/tokens/${tokenId}`; return this.retryUntilData(url); } + + async getTokenRelationships(accountId) { + const url = `${this.mirrorNodeRestUrl}/api/v1/accounts/${accountId}/tokens`; + return this.retryUntilData(url); + } async retryUntilData(url) { const maxRetries = Math.floor(this.NODE_TIMEOUT / 1000); // retry once per second diff --git a/test/token-service/test_tokenDissociateTransaction.js b/test/token-service/test_tokenDissociateTransaction.js new file mode 100644 index 0000000..62e9dac --- /dev/null +++ b/test/token-service/test_tokenDissociateTransaction.js @@ -0,0 +1,468 @@ +import { assert, expect } from "chai"; +import { JSONRPCRequest } from "../../client.js"; +import mirrorNodeClient from "../../mirrorNodeClient.js"; +import { setOperator } from "../../setup_Tests.js"; +import { retryOnError } from "../../utils/helpers/retry-on-error.js"; + +/** + * Tests for TokenDissociateTransaction + */ +describe("TokenDissociateTransaction", function () { + // Tests should not take longer than 30 seconds to fully execute. + this.timeout(30000); + + // All tests require an account and a token to be created, and to have the two be associated. + let tokenId, tokenKey, accountId, accountPrivateKey; + beforeEach(async function () { + await setOperator( + process.env.OPERATOR_ACCOUNT_ID, + process.env.OPERATOR_ACCOUNT_PRIVATE_KEY, + ); + + response = await JSONRPCRequest(this, "generateKey", { + type: "ed25519PrivateKey", + }); + tokenKey = response.key; + + let response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + freezeKey: tokenKey, + tokenType: "ft", + pauseKey: tokenKey + }); + tokenId = response.tokenId; + + response = await JSONRPCRequest(this, "generateKey", { + type: "ed25519PrivateKey", + }); + accountPrivateKey = response.key; + + response = await JSONRPCRequest(this, "createAccount", { + key: accountPrivateKey, + }); + accountId = response.accountId; + + await JSONRPCRequest(this, "associateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey] + } + }); + }); + afterEach(async function () { + await JSONRPCRequest(this, "reset"); + }); + + async function verifyTokenAssociation(accountId, tokenId) { + // No way to get token associations via consensus node, so just query mirror node. + const mirrorNodeInfo = + await mirrorNodeClient.getTokenRelationships(accountId); + + let foundToken = false; + for (let i = 0; i < mirrorNodeInfo.tokens.length; i++) { + if (mirrorNodeInfo.tokens[i].token_id === tokenId) { + foundToken = true; + break; + } + } + + expect(foundToken).to.be.true; + } + + async function verifyNoTokenAssociations(accountId) { + // No way to get token associations via consensus node, so just query mirror node. + const mirrorNodeInfo = + await mirrorNodeClient.getTokenRelationships(accountId); + expect(mirrorNodeInfo.tokens.length).to.equal(0); + } + + describe("Account ID", function () { + it("(#1) Dissociates a token from an account", async function () { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + + await retryOnError(async () => + verifyNoTokenAssociations(accountId), + ); + }); + + it("(#2) Dissociates a token from an account with which it is already dissociated", async function () { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#3) Dissociates a token from an account without signing with the account's private key", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + }); + } catch (err) { + assert.equal(err.data.status, "INVALID_SIGNATURE"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#4) Dissociates a token from an account that doesn't exist", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId: "123.456.789", + tokenIds: [tokenId], + }); + } catch (err) { + assert.equal(err.data.status, "INVALID_ACCOUNT_ID"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#5) Dissociates a token from an account that is deleted", async function () { + await JSONRPCRequest(this, "deleteAccount", { + deleteAccountId: accountId, + transferAccountId: process.env.OPERATOR_ACCOUNT_ID, + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "ACCOUNT_DELETED"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#6) Dissociates a token from an empty account", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId: "", + tokenIds: [tokenId], + }); + } catch (err) { + assert.equal(err.code, -32603, "Internal error"); + return; + } + + assert.fail("Should throw an error"); + }); + }); + + describe("Token IDs", function () { + it("(#1) Dissociates no tokens from an account", async function () { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + + await retryOnError(async () => verifyTokenAssociation(accountId, tokenId)); + }); + + it("(#2) Dissociates a token that doesn't exist from an account", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: ["123.456.789"], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "INVALID_TOKEN_ID"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#3) Dissociates a token that is deleted from an account", async function () { + let response = await JSONRPCRequest(this, "generateKey", { + type: "ecdsaSecp256k1PrivateKey", + }); + const adminKey = response.key; + + response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + adminKey, + tokenType: "ft", + commonTransactionParams: { + signers: [adminKey], + }, + }); + const deletedTokenId = response.tokenId; + + await JSONRPCRequest(this, "deleteToken", { + tokenId: deletedTokenId, + commonTransactionParams: { + signers: [adminKey], + }, + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [deletedTokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TOKEN_WAS_DELETED"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#4) Dissociates a token that is empty from an account", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [""], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.code, -32603, "Internal error"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#5) Dissociates a token twice from an account", async function () { + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId, tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TOKEN_ID_REPEATED_IN_TOKEN_LIST"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#6) Dissociates three valid tokens from an account", async function () { + let response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }); + const secondTokenId = response.tokenId; + + response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }); + const thirdTokenId = response.tokenId; + + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId, secondTokenId, thirdTokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + + await retryOnError(async () => verifyNoTokenAssociations(accountId), + ); + }); + + it("(#7) Dissociates two valid tokens and an invalid token from an account", async function () { + const response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }); + const secondTokenId = response.tokenId; + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId, secondTokenId, "123.456.789"], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "INVALID_TOKEN_ID"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#8) Dissociates two valid tokens and a deleted token from an account", async function () { + let response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }); + const secondTokenId = response.tokenId; + + response = await JSONRPCRequest(this, "generateKey", { + type: "ecdsaSecp256k1PrivateKey", + }); + const adminKey = response.key; + + response = await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + adminKey, + tokenType: "ft", + commonTransactionParams: { + signers: [adminKey], + }, + }); + const deletedTokenId = response.tokenId; + + await JSONRPCRequest(this, "deleteToken", { + tokenId: deletedTokenId, + commonTransactionParams: { + signers: [adminKey], + }, + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId, secondTokenId, deletedTokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TOKEN_WAS_DELETED"); + return; + } + + assert.fail("Should throw an error"); + }); + + it.skip("(#9) Dissociates a token from an account while that account has a balance of the token", async function () { + // TODO: implement TransferTransaction here and transfer balance of token to account. + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#10) Dissociates a token from an account while its frozen for the account", async function () { + await JSONRPCRequest(this, "freezeToken", { + tokenId, + accountId, + commonTransactionParams: { + signers: [tokenKey] + } + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "ACCOUNT_FROZEN_FOR_TOKEN"); + return; + } + + assert.fail("Should throw an error"); + }); + + it("(#11) Dissociates a token from an account while the token is paused", async function () { + await JSONRPCRequest(this, "pauseToken", { + tokenId, + accountId, + commonTransactionParams: { + signers: [tokenKey] + } + }); + + try { + await JSONRPCRequest(this, "dissociateToken", { + accountId, + tokenIds: [tokenId], + commonTransactionParams: { + signers: [accountPrivateKey], + }, + }); + } catch (err) { + assert.equal(err.data.status, "TOKEN_IS_PAUSED"); + return; + } + + assert.fail("Should throw an error"); + }); + }); + + return Promise.resolve(); +}); \ No newline at end of file From 263275a29bb44972bb584d7dc7c17b7f7b0cda99 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Mon, 25 Nov 2024 17:25:13 -0500 Subject: [PATCH 2/5] fix: tests and use correct response codes Signed-off-by: Rob Walworth --- .../test_tokenDissociateTransaction.js | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/token-service/test_tokenDissociateTransaction.js b/test/token-service/test_tokenDissociateTransaction.js index 62e9dac..480ebdf 100644 --- a/test/token-service/test_tokenDissociateTransaction.js +++ b/test/token-service/test_tokenDissociateTransaction.js @@ -19,12 +19,12 @@ describe("TokenDissociateTransaction", function () { process.env.OPERATOR_ACCOUNT_PRIVATE_KEY, ); - response = await JSONRPCRequest(this, "generateKey", { + let response = await JSONRPCRequest(this, "generateKey", { type: "ed25519PrivateKey", }); tokenKey = response.key; - let response = await JSONRPCRequest(this, "createToken", { + response = await JSONRPCRequest(this, "createToken", { name: "testname", symbol: "testsymbol", treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, @@ -209,7 +209,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err) { - assert.equal(err.data.status, "INVALID_TOKEN_ID"); + assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); return; } @@ -250,7 +250,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err) { - assert.equal(err.data.status, "TOKEN_WAS_DELETED"); + assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); return; } @@ -308,6 +308,14 @@ describe("TokenDissociateTransaction", function () { }); const thirdTokenId = response.tokenId; + await JSONRPCRequest(this, "associateToken", { + accountId, + tokenIds: [secondTokenId, thirdTokenId], + commonTransactionParams: { + signers: [accountPrivateKey] + } + }) + await JSONRPCRequest(this, "dissociateToken", { accountId, tokenIds: [tokenId, secondTokenId, thirdTokenId], @@ -338,7 +346,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err) { - assert.equal(err.data.status, "INVALID_TOKEN_ID"); + assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); return; } @@ -387,7 +395,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err) { - assert.equal(err.data.status, "TOKEN_WAS_DELETED"); + assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); return; } From 91d086b60a0989693ffdaecb0919ef8d42b03194 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Mon, 25 Nov 2024 17:31:52 -0500 Subject: [PATCH 3/5] docs: update correct response codes Signed-off-by: Rob Walworth --- ...ction.md => tokenDissociateTransaction.md} | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) rename test-specifications/token-service/{tokenDisscoiateTransaction.md => tokenDissociateTransaction.md} (91%) diff --git a/test-specifications/token-service/tokenDisscoiateTransaction.md b/test-specifications/token-service/tokenDissociateTransaction.md similarity index 91% rename from test-specifications/token-service/tokenDisscoiateTransaction.md rename to test-specifications/token-service/tokenDissociateTransaction.md index 4f5d1f7..778e52c 100644 --- a/test-specifications/token-service/tokenDisscoiateTransaction.md +++ b/test-specifications/token-service/tokenDissociateTransaction.md @@ -54,12 +54,12 @@ The tests contained in this specification will assume that a valid account and a | Test no | Name | Input | Expected response | Implemented (Y/N) | |---------|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------|-------------------| -| 1 | Dissociates a token from an account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation succeeds and the token is associated with . | N | -| 2 | Dissociates a token from an account with which it is already dissociated | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | N | -| 3 | Dissociates a token from an account without signing with the account's private key | accountId=, tokenIds=[] | The token dissociation fails with an INVALID_SIGNATURE response code from the network. | N | -| 4 | Dissociates a token from an account that doesn't exist | accountId="123.456.789", tokenIds=[] | The token dissociation fails with an INVALID_ACCOUNT_ID response code from the network. | N | -| 5 | Dissociates a token from an account that is deleted | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an INVALID_ACCOUNT_ID response code from the network. | N | -| 6 | Dissociates a token from an empty account | accountId="", tokenIds=[] | The token dissociation fails with an SDK internal error. | N | +| 1 | Dissociates a token from an account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation succeeds and the token is associated with . | Y | +| 2 | Dissociates a token from an account with which it is already dissociated | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | +| 3 | Dissociates a token from an account without signing with the account's private key | accountId=, tokenIds=[] | The token dissociation fails with an INVALID_SIGNATURE response code from the network. | Y | +| 4 | Dissociates a token from an account that doesn't exist | accountId="123.456.789", tokenIds=[] | The token dissociation fails with an INVALID_ACCOUNT_ID response code from the network. | Y | +| 5 | Dissociates a token from an account that is deleted | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an INVALID_ACCOUNT_ID response code from the network. | Y | +| 6 | Dissociates a token from an empty account | accountId="", tokenIds=[] | The token dissociation fails with an SDK internal error. | Y | #### JSON Request Example @@ -101,17 +101,17 @@ The tests contained in this specification will assume that a valid account and a | Test no | Name | Input | Expected response | Implemented (Y/N) | |---------|-----------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|-------------------| -| 1 | Dissociates no tokens from an account | accountId=, commonTransactionParams.signers=[] | The token dissociation succeeds and no disassociations are made. | N | -| 2 | Dissociates a token that doesn't exist from an account | accountId=, tokenIds=["123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an INVALID_TOKEN_ID response code from the network. | N | -| 3 | Dissociates a token that is deleted from an account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_WAS_DELETED response code from the network. | N | -| 4 | Dissociates a token that is empty from an account | accountId=, tokenIds=[""], commonTransactionParams.signers=[] | The token dissociation fails with an SDK internal error. | N | -| 5 | Dissociates a token twice from an account | accountId=, tokenIds=[, ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_ID_REPEATED_IN_TOKEN_LIST response code from the network. | N | -| 6 | Dissociates three valid tokens from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation succeeds and three disassociations are made. | N | -| 7 | Dissociates two valid tokens and an invalid token from an account | accountId=, tokenIds=[, , "123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an INVALID_TOKEN_ID response code from the network. | N | -| 8 | Dissociates two valid tokens and a deleted token from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_WAS_DELETED response code from the network. | N | +| 1 | Dissociates no tokens from an account | accountId=, commonTransactionParams.signers=[] | The token dissociation succeeds and no disassociations are made. | Y | +| 2 | Dissociates a token that doesn't exist from an account | accountId=, tokenIds=["123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | +| 3 | Dissociates a token that is deleted from an account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | +| 4 | Dissociates a token that is empty from an account | accountId=, tokenIds=[""], commonTransactionParams.signers=[] | The token dissociation fails with an SDK internal error. | Y | +| 5 | Dissociates a token twice from an account | accountId=, tokenIds=[, ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_ID_REPEATED_IN_TOKEN_LIST response code from the network. | Y | +| 6 | Dissociates three valid tokens from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation succeeds and three disassociations are made. | Y | +| 7 | Dissociates two valid tokens and an invalid token from an account | accountId=, tokenIds=[, , "123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | +| 8 | Dissociates two valid tokens and a deleted token from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | | 9 | Dissociates a token from an account while that account has a balance of the token | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES response code from the network. | N | -| 10 | Dissociates a token from an account while its frozen for the account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an ACCOUNT_FROZEN_FOR_TOKEN response code from the network. | N | -| 11 | Dissociates a token from an account while the token is paused | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_IS_PAUSED response code from the network. | N | +| 10 | Dissociates a token from an account while its frozen for the account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an ACCOUNT_FROZEN_FOR_TOKEN response code from the network. | Y | +| 11 | Dissociates a token from an account while the token is paused | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_IS_PAUSED response code from the network. | Y | #### JSON Request Example From ebd044d702d34ef994b5c3a0d36d7246579839c0 Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Tue, 17 Dec 2024 14:19:55 -0500 Subject: [PATCH 4/5] refactor: address PR comments Signed-off-by: Rob Walworth --- .../TokenDissociateTransaction.md | 4 +- .../test-token-dissociate-transaction.ts | 197 +++++++++++------- 2 files changed, 118 insertions(+), 83 deletions(-) diff --git a/docs/test-specifications/token-service/TokenDissociateTransaction.md b/docs/test-specifications/token-service/TokenDissociateTransaction.md index 778e52c..674f2d0 100644 --- a/docs/test-specifications/token-service/TokenDissociateTransaction.md +++ b/docs/test-specifications/token-service/TokenDissociateTransaction.md @@ -107,8 +107,8 @@ The tests contained in this specification will assume that a valid account and a | 4 | Dissociates a token that is empty from an account | accountId=, tokenIds=[""], commonTransactionParams.signers=[] | The token dissociation fails with an SDK internal error. | Y | | 5 | Dissociates a token twice from an account | accountId=, tokenIds=[, ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_ID_REPEATED_IN_TOKEN_LIST response code from the network. | Y | | 6 | Dissociates three valid tokens from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation succeeds and three disassociations are made. | Y | -| 7 | Dissociates two valid tokens and an invalid token from an account | accountId=, tokenIds=[, , "123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | -| 8 | Dissociates two valid tokens and a deleted token from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_NOT_ASSOCIATED_TO_ACCOUNT response code from the network. | Y | +| 7 | Dissociates two valid and associated tokens and an invalid token from an account | accountId=, tokenIds=[, , "123.456.789"], commonTransactionParams.signers=[] | The token dissociation fails with an INVALID_TOKEN_ID response code from the network. | Y | +| 8 | Dissociates two valid and associated tokens and a deleted token from an account | accountId=, tokenIds=[, , ], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_WAS_DELETED response code from the network. | Y | | 9 | Dissociates a token from an account while that account has a balance of the token | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES response code from the network. | N | | 10 | Dissociates a token from an account while its frozen for the account | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an ACCOUNT_FROZEN_FOR_TOKEN response code from the network. | Y | | 11 | Dissociates a token from an account while the token is paused | accountId=, tokenIds=[], commonTransactionParams.signers=[] | The token dissociation fails with an TOKEN_IS_PAUSED response code from the network. | Y | diff --git a/src/tests/token-service/test-token-dissociate-transaction.ts b/src/tests/token-service/test-token-dissociate-transaction.ts index a9b00a2..ec4a135 100644 --- a/src/tests/token-service/test-token-dissociate-transaction.ts +++ b/src/tests/token-service/test-token-dissociate-transaction.ts @@ -5,6 +5,7 @@ import mirrorNodeClient from "@services/MirrorNodeClient"; import { setOperator } from "@helpers/setup-tests"; import { retryOnError } from "@helpers/retry-on-error"; +import { ErrorStatusCodes } from "@enums/error-status-codes"; /** * Tests for TokenDissociateTransaction @@ -25,30 +26,34 @@ describe("TokenDissociateTransaction", function () { process.env.OPERATOR_ACCOUNT_PRIVATE_KEY as string, ); - let response = await JSONRPCRequest(this, "generateKey", { - type: "ed25519PrivateKey", - }); - tokenKey = response.key; - - response = await JSONRPCRequest(this, "createToken", { - name: "testname", - symbol: "testsymbol", - treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, - freezeKey: tokenKey, - tokenType: "ft", - pauseKey: tokenKey, - }); - tokenId = response.tokenId; - - response = await JSONRPCRequest(this, "generateKey", { - type: "ed25519PrivateKey", - }); - accountPrivateKey = response.key; + tokenKey = ( + await JSONRPCRequest(this, "generateKey", { + type: "ed25519PrivateKey", + }) + ).key; - response = await JSONRPCRequest(this, "createAccount", { - key: accountPrivateKey, - }); - accountId = response.accountId; + tokenId = ( + await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + freezeKey: tokenKey, + tokenType: "ft", + pauseKey: tokenKey, + }) + ).tokenId; + + accountPrivateKey = ( + await JSONRPCRequest(this, "generateKey", { + type: "ed25519PrivateKey", + }) + ).key; + + accountId = ( + await JSONRPCRequest(this, "createAccount", { + key: accountPrivateKey, + }) + ).accountId; await JSONRPCRequest(this, "associateToken", { accountId, @@ -95,7 +100,9 @@ describe("TokenDissociateTransaction", function () { }, }); - await retryOnError(async () => verifyNoTokenAssociations(accountId)); + await retryOnError(async () => { + await verifyNoTokenAssociations(accountId); + }); }); it("(#2) Dissociates a token from an account with which it is already dissociated", async function () { @@ -183,7 +190,11 @@ describe("TokenDissociateTransaction", function () { tokenIds: [tokenId], }); } catch (err: any) { - assert.equal(err.code, -32603, "Internal error"); + assert.equal( + err.code, + ErrorStatusCodes.INTERNAL_ERROR, + "Internal error", + ); return; } @@ -200,9 +211,9 @@ describe("TokenDissociateTransaction", function () { }, }); - await retryOnError(async () => - verifyTokenAssociation(accountId, tokenId), - ); + await retryOnError(async () => { + verifyTokenAssociation(accountId, tokenId); + }); }); it("(#2) Dissociates a token that doesn't exist from an account", async function () { @@ -223,22 +234,24 @@ describe("TokenDissociateTransaction", function () { }); it("(#3) Dissociates a token that is deleted from an account", async function () { - let response = await JSONRPCRequest(this, "generateKey", { - type: "ecdsaSecp256k1PrivateKey", - }); - const adminKey = response.key; - - response = await JSONRPCRequest(this, "createToken", { - name: "testname", - symbol: "testsymbol", - treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, - adminKey, - tokenType: "ft", - commonTransactionParams: { - signers: [adminKey], - }, - }); - const deletedTokenId = response.tokenId; + const adminKey = ( + await JSONRPCRequest(this, "generateKey", { + type: "ecdsaSecp256k1PrivateKey", + }) + ).key; + + const deletedTokenId = ( + await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + adminKey, + tokenType: "ft", + commonTransactionParams: { + signers: [adminKey], + }, + }) + ).tokenId; await JSONRPCRequest(this, "deleteToken", { tokenId: deletedTokenId, @@ -256,7 +269,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err: any) { - assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); + assert.equal(err.data.status, "TOKEN_WAS_DELETED"); return; } @@ -273,7 +286,11 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err: any) { - assert.equal(err.code, -32603, "Internal error"); + assert.equal( + err.code, + ErrorStatusCodes.INTERNAL_ERROR, + "Internal error", + ); return; } @@ -298,21 +315,23 @@ describe("TokenDissociateTransaction", function () { }); it("(#6) Dissociates three valid tokens from an account", async function () { - let response = await JSONRPCRequest(this, "createToken", { - name: "testname", - symbol: "testsymbol", - treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, - tokenType: "ft", - }); - const secondTokenId = response.tokenId; - - response = await JSONRPCRequest(this, "createToken", { - name: "testname", - symbol: "testsymbol", - treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, - tokenType: "ft", - }); - const thirdTokenId = response.tokenId; + const secondTokenId = ( + await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }) + ).tokenId; + + const thirdTokenId = ( + await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }) + ).tokenId; await JSONRPCRequest(this, "associateToken", { accountId, @@ -330,17 +349,28 @@ describe("TokenDissociateTransaction", function () { }, }); - await retryOnError(async () => verifyNoTokenAssociations(accountId)); + await retryOnError(async () => { + await verifyNoTokenAssociations(accountId); + }); }); - it("(#7) Dissociates two valid tokens and an invalid token from an account", async function () { - const response = await JSONRPCRequest(this, "createToken", { - name: "testname", - symbol: "testsymbol", - treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, - tokenType: "ft", + it("(#7) Dissociates two valid and associated tokens and an invalid token from an account", async function () { + const secondTokenId = ( + await JSONRPCRequest(this, "createToken", { + name: "testname", + symbol: "testsymbol", + treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, + tokenType: "ft", + }) + ).tokenId; + + await JSONRPCRequest(this, "associateToken", { + accountId, + tokenId: secondTokenId, + commonTransactionParams: { + signers: [accountPrivateKey] + } }); - const secondTokenId = response.tokenId; try { await JSONRPCRequest(this, "dissociateToken", { @@ -351,28 +381,26 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err: any) { - assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); + assert.equal(err.data.status, "INVALID_TOKEN_ID"); return; } assert.fail("Should throw an error"); }); - it("(#8) Dissociates two valid tokens and a deleted token from an account", async function () { - let response = await JSONRPCRequest(this, "createToken", { + it("(#8) Dissociates two valid and associated tokens and a deleted token from an account", async function () { + const secondTokenId = (await JSONRPCRequest(this, "createToken", { name: "testname", symbol: "testsymbol", treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, tokenType: "ft", - }); - const secondTokenId = response.tokenId; + })).tokenId; - response = await JSONRPCRequest(this, "generateKey", { + const adminKey = (await JSONRPCRequest(this, "generateKey", { type: "ecdsaSecp256k1PrivateKey", - }); - const adminKey = response.key; + })).key; - response = await JSONRPCRequest(this, "createToken", { + const deletedTokenId = (await JSONRPCRequest(this, "createToken", { name: "testname", symbol: "testsymbol", treasuryAccountId: process.env.OPERATOR_ACCOUNT_ID, @@ -381,8 +409,7 @@ describe("TokenDissociateTransaction", function () { commonTransactionParams: { signers: [adminKey], }, - }); - const deletedTokenId = response.tokenId; + })).tokenId; await JSONRPCRequest(this, "deleteToken", { tokenId: deletedTokenId, @@ -391,6 +418,14 @@ describe("TokenDissociateTransaction", function () { }, }); + await JSONRPCRequest(this, "associateToken", { + accountId, + tokenIds: [secondTokenId], + commonTransactionParams: { + signers: [accountPrivateKey] + } + }); + try { await JSONRPCRequest(this, "dissociateToken", { accountId, @@ -400,7 +435,7 @@ describe("TokenDissociateTransaction", function () { }, }); } catch (err: any) { - assert.equal(err.data.status, "TOKEN_NOT_ASSOCIATED_TO_ACCOUNT"); + assert.equal(err.data.status, "TOKEN_WAS_DELETED"); return; } From efaf26e40e39ffcae3be6a602fb75b8594cf4b6e Mon Sep 17 00:00:00 2001 From: Rob Walworth Date: Tue, 17 Dec 2024 14:42:55 -0500 Subject: [PATCH 5/5] fix: skip some tests that had incorrect response codes Signed-off-by: Rob Walworth --- .../token-service/test-token-dissociate-transaction.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tests/token-service/test-token-dissociate-transaction.ts b/src/tests/token-service/test-token-dissociate-transaction.ts index ec4a135..f63f632 100644 --- a/src/tests/token-service/test-token-dissociate-transaction.ts +++ b/src/tests/token-service/test-token-dissociate-transaction.ts @@ -233,7 +233,7 @@ describe("TokenDissociateTransaction", function () { assert.fail("Should throw an error"); }); - it("(#3) Dissociates a token that is deleted from an account", async function () { + it.skip("(#3) Dissociates a token that is deleted from an account", async function () { const adminKey = ( await JSONRPCRequest(this, "generateKey", { type: "ecdsaSecp256k1PrivateKey", @@ -354,7 +354,7 @@ describe("TokenDissociateTransaction", function () { }); }); - it("(#7) Dissociates two valid and associated tokens and an invalid token from an account", async function () { + it.skip("(#7) Dissociates two valid and associated tokens and an invalid token from an account", async function () { const secondTokenId = ( await JSONRPCRequest(this, "createToken", { name: "testname", @@ -388,7 +388,7 @@ describe("TokenDissociateTransaction", function () { assert.fail("Should throw an error"); }); - it("(#8) Dissociates two valid and associated tokens and a deleted token from an account", async function () { + it.skip("(#8) Dissociates two valid and associated tokens and a deleted token from an account", async function () { const secondTokenId = (await JSONRPCRequest(this, "createToken", { name: "testname", symbol: "testsymbol",