diff --git a/packages/wallet-service/src/fullnode.ts b/packages/wallet-service/src/fullnode.ts index c71c01da..8a710a0c 100644 --- a/packages/wallet-service/src/fullnode.ts +++ b/packages/wallet-service/src/fullnode.ts @@ -32,7 +32,7 @@ export const create = (baseURL = BASE_URL) => { throw new Error(error.message); } - return value as FullNodeApiVersionResponse; + return value; }; const downloadTx = async (txId: string) => { diff --git a/packages/wallet-service/src/types.ts b/packages/wallet-service/src/types.ts index b282e3cd..0fbd8218 100644 --- a/packages/wallet-service/src/types.ts +++ b/packages/wallet-service/src/types.ts @@ -126,7 +126,7 @@ export interface FullNodeApiVersionResponse { genesis_block_hash?: string, genesis_tx1_hash?: string, genesis_tx2_hash?: string, - native_token?: { name: string, symbol: string }; + native_token?: { name: string, symbol: string, version?: number }; } export interface TxProposal { diff --git a/packages/wallet-service/tests/fullnode.test.ts b/packages/wallet-service/tests/fullnode.test.ts index 4fc3a02c..f60e3b33 100644 --- a/packages/wallet-service/tests/fullnode.test.ts +++ b/packages/wallet-service/tests/fullnode.test.ts @@ -1,4 +1,60 @@ import fullnode from '@src/fullnode'; +import { defaultTestVersionData } from '@tests/utils'; + +describe('version', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('returns parsed payload when native_token includes the version field', async () => { + expect.hasAssertions(); + + const payload = { + ...defaultTestVersionData(), + native_token: { name: 'Hathor', symbol: 'HTR', version: 0 }, + }; + const apiGetSpy = jest.spyOn(fullnode.api, 'get'); + apiGetSpy.mockImplementation(() => Promise.resolve({ + status: 200, + data: payload, + })); + + const response = await fullnode.version(); + expect(response).toStrictEqual(payload); + }); + + test('returns parsed payload when native_token omits the version field', async () => { + expect.hasAssertions(); + + // defaultTestVersionData() returns a payload whose native_token has no `version`. + const payload = defaultTestVersionData(); + const apiGetSpy = jest.spyOn(fullnode.api, 'get'); + apiGetSpy.mockImplementation(() => Promise.resolve({ + status: 200, + data: payload, + })); + + const response = await fullnode.version(); + expect(response).toStrictEqual(payload); + expect(response.native_token).not.toHaveProperty('version'); + }); + + test('throws when the response fails schema validation', async () => { + expect.hasAssertions(); + + const invalidPayload = { + ...defaultTestVersionData(), + native_token: { name: 'Hathor', symbol: 'HTR', version: 1.5 }, + }; + const apiGetSpy = jest.spyOn(fullnode.api, 'get'); + apiGetSpy.mockImplementation(() => Promise.resolve({ + status: 200, + data: invalidPayload, + })); + + await expect(fullnode.version()).rejects.toThrow(/native_token\.version/); + }); +}); test('downloadTx', async () => { expect.hasAssertions(); diff --git a/packages/wallet-service/tests/schemas.test.ts b/packages/wallet-service/tests/schemas.test.ts new file mode 100644 index 00000000..f491e77e --- /dev/null +++ b/packages/wallet-service/tests/schemas.test.ts @@ -0,0 +1,43 @@ +import { FullnodeVersionSchema } from '@src/schemas'; +import { defaultTestVersionData } from '@tests/utils'; + +// Regression guard: the `native_token.version` is not available in all live fullnode instances. +// The Wallet Service must be able to handle both cases. +test('FullnodeVersionSchema regression: native_token.version is accepted', () => { + const payload = { + ...defaultTestVersionData(), + native_token: { name: 'Hathor', symbol: 'HTR', version: 0 }, + }; + const { error, value } = FullnodeVersionSchema.validate(payload); + expect(error).toBeUndefined(); + expect(value.native_token).toEqual({ name: 'Hathor', symbol: 'HTR', version: 0 }); +}); + +test('FullnodeVersionSchema accepts a legacy fullnode payload without native_token.version', () => { + // defaultTestVersionData() returns a payload whose native_token has no `version`. + const { error } = FullnodeVersionSchema.validate(defaultTestVersionData()); + expect(error).toBeUndefined(); +}); + +// Design-intent guard: native_token is intentionally strict — any unknown +// field there fails validation so a contract test (and not a production +// outage) surfaces fullnode schema drift. +test('FullnodeVersionSchema rejects unknown fields under native_token', () => { + const payload = { + ...defaultTestVersionData(), + native_token: { name: 'Hathor', symbol: 'HTR', version: 0, icon: 'data:image/png;base64,...' }, + }; + const { error } = FullnodeVersionSchema.validate(payload); + expect(error).toBeDefined(); + expect(error!.message).toMatch(/native_token\.icon/); +}); + +test('FullnodeVersionSchema rejects a non-integer native_token.version', () => { + const payload = { + ...defaultTestVersionData(), + native_token: { name: 'Hathor', symbol: 'HTR', version: 1.5 }, + }; + const { error } = FullnodeVersionSchema.validate(payload); + expect(error).toBeDefined(); + expect(error!.message).toMatch(/native_token\.version/); +});