Skip to content
Draft
Changes from 2 commits
Commits
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
100 changes: 68 additions & 32 deletions packages/wasm-sdk/tests/unit/data-contract.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,47 @@ import init, * as sdk from '../../dist/sdk.compressed.js';
import contractFixtureV0 from './fixtures/data-contract-v0-crypto-card-game.mjs';
import contractFixtureV1 from './fixtures/data-contract-v1-with-docs-tokens-groups.mjs';

// Platform version constants
const PLATFORM_VERSION_CONTRACT_V0 = 1;
const PLATFORM_VERSION_CONTRACT_V1 = 9; // V1 contracts introduced in Platform v9
// Platform version configuration
const PLATFORM_VERSIONS = {
MIN: 1,
MAX: 10, // Update as new platform versions are released
};

// Contract format version configuration
// To add a new format version, add an entry here with:
// - The Platform version when introduced
// - A fixture for the new format
// Example: V2: { introduced: 12, fixture: contractFixtureV2 }
const CONTRACT_FORMAT_VERSIONS = {
V0: { introduced: 1, fixture: contractFixtureV0 },
V1: { introduced: 9, fixture: contractFixtureV1 },
};

// Auto-generate compatibility data for all formats
const FORMATS = Object.entries(CONTRACT_FORMAT_VERSIONS).reduce((acc, [formatKey, config]) => {
const compatibleVersions = Array.from(
{ length: PLATFORM_VERSIONS.MAX - config.introduced + 1 },
(_, i) => i + config.introduced
);

const allVersions = Array.from(
{ length: PLATFORM_VERSIONS.MAX - PLATFORM_VERSIONS.MIN + 1 },
(_, i) => i + PLATFORM_VERSIONS.MIN
);

const incompatibleVersions = allVersions.filter(v => v < config.introduced);

// Platform version compatibility ranges
const V0_COMPATIBLE_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // V0 works across all versions
const V1_COMPATIBLE_VERSIONS = [9, 10]; // V1 only works from version 9+
const V0_ONLY_VERSIONS = [1, 2, 3, 4, 5, 6, 7, 8]; // Versions that only support V0
const LATEST_KNOWN_VERSION = Math.max(...V0_COMPATIBLE_VERSIONS);
acc[formatKey] = {
...config,
compatibleVersions,
incompatibleVersions,
platformVersion: config.introduced,
};

return acc;
}, {});

const LATEST_KNOWN_VERSION = PLATFORM_VERSIONS.MAX;

// Helper function for testing contract compatibility across versions
const testContractAcrossVersions = (
Expand Down Expand Up @@ -48,7 +80,7 @@ describe('DataContract', () => {

describe('Contract Creation', () => {
it('should create a V0 contract from JSON and expose all properties', async () => {
const contract = sdk.DataContract.fromJSON(contractFixtureV0, PLATFORM_VERSION_CONTRACT_V0);
const contract = sdk.DataContract.fromJSON(contractFixtureV0, FORMATS.V0.platformVersion);

expect(contract).to.be.ok();
expect(contract.id()).to.equal(contractFixtureV0.id);
Expand All @@ -74,7 +106,7 @@ describe('DataContract', () => {

// TODO: enable test once an SDK fix to support this is merged
it.skip('should create a V1 contract from JSON and expose all properties including tokens and groups', async () => {
const contract = sdk.DataContract.fromJSON(contractFixtureV1, PLATFORM_VERSION_CONTRACT_V1);
const contract = sdk.DataContract.fromJSON(contractFixtureV1, FORMATS.V1.platformVersion);

expect(contract).to.be.ok();
expect(contract.id()).to.equal(contractFixtureV1.id);
Expand Down Expand Up @@ -105,7 +137,7 @@ describe('DataContract', () => {

it('should create a contract with only document schemas (no tokens)', () => {
// V0 fixture already has only documents, no tokens - verify it works
const contract = sdk.DataContract.fromJSON(contractFixtureV0, PLATFORM_VERSION_CONTRACT_V0);
const contract = sdk.DataContract.fromJSON(contractFixtureV0, FORMATS.V0.platformVersion);
const roundTripped = contract.toJSON();

expect(roundTripped.documentSchemas.card).to.exist();
Expand All @@ -123,7 +155,7 @@ describe('DataContract', () => {

const contract = sdk.DataContract.fromJSON(
contractWithOnlyTokens,
PLATFORM_VERSION_CONTRACT_V1,
FORMATS.V1.platformVersion,
);
const roundTripped = contract.toJSON();

Expand All @@ -136,23 +168,23 @@ describe('DataContract', () => {
describe('Version Compatibility', () => {
it('should fail to create a V1 contract with V0 platform version', async () => {
expect(() => {
sdk.DataContract.fromJSON(contractFixtureV1, PLATFORM_VERSION_CONTRACT_V0);
sdk.DataContract.fromJSON(contractFixtureV1, FORMATS.V0.platformVersion);
}).to.throw(/dpp unknown version.*known versions.*\[0\].*received.*1/);
});
});

describe('Validation', () => {
it('should handle invalid JSON input gracefully', () => {
expect(() => {
sdk.DataContract.fromJSON(null, PLATFORM_VERSION_CONTRACT_V0);
sdk.DataContract.fromJSON(null, FORMATS.V0.platformVersion);
}).to.throw();

expect(() => {
sdk.DataContract.fromJSON({}, PLATFORM_VERSION_CONTRACT_V0);
sdk.DataContract.fromJSON({}, FORMATS.V0.platformVersion);
}).to.throw();

expect(() => {
sdk.DataContract.fromJSON({ id: 'invalid' }, PLATFORM_VERSION_CONTRACT_V0);
sdk.DataContract.fromJSON({ id: 'invalid' }, FORMATS.V0.platformVersion);
}).to.throw();
});

Expand All @@ -162,23 +194,23 @@ describe('DataContract', () => {
sdk.DataContract.fromJSON({
...contractFixtureV0,
id: 'invalid-not-base58!',
}, PLATFORM_VERSION_CONTRACT_V0);
}, FORMATS.V0.platformVersion);
}).to.throw();

// Test negative version number
expect(() => {
sdk.DataContract.fromJSON({
...contractFixtureV0,
version: -1,
}, PLATFORM_VERSION_CONTRACT_V0);
}, FORMATS.V0.platformVersion);
}).to.throw();

// Test invalid ownerId
expect(() => {
sdk.DataContract.fromJSON({
...contractFixtureV0,
ownerId: 'not-a-valid-id',
}, PLATFORM_VERSION_CONTRACT_V0);
}, FORMATS.V0.platformVersion);
}).to.throw();
});

Expand All @@ -193,18 +225,18 @@ describe('DataContract', () => {
};

expect(() => {
sdk.DataContract.fromJSON(contractWithEmptySchemas, PLATFORM_VERSION_CONTRACT_V0);
sdk.DataContract.fromJSON(contractWithEmptySchemas, FORMATS.V0.platformVersion);
}).to.throw(/must have at least one document type or token defined/);
});
});

describe('Data Preservation', () => {
it('should preserve all data through JSON round-trip for V0 contract', async () => {
const contract = sdk.DataContract.fromJSON(contractFixtureV0, PLATFORM_VERSION_CONTRACT_V0);
const contract = sdk.DataContract.fromJSON(contractFixtureV0, FORMATS.V0.platformVersion);
const roundTripped = contract.toJSON();

// Create a new contract from the round-tripped JSON
const contract2 = sdk.DataContract.fromJSON(roundTripped, PLATFORM_VERSION_CONTRACT_V0);
const contract2 = sdk.DataContract.fromJSON(roundTripped, FORMATS.V0.platformVersion);
const roundTripped2 = contract2.toJSON();

expect(roundTripped2).to.deep.equal(roundTripped);
Expand All @@ -214,11 +246,11 @@ describe('DataContract', () => {
});

it('should preserve all data through JSON round-trip for V1 contract', async () => {
const contract = sdk.DataContract.fromJSON(contractFixtureV1, PLATFORM_VERSION_CONTRACT_V1);
const contract = sdk.DataContract.fromJSON(contractFixtureV1, FORMATS.V1.platformVersion);
const roundTripped = contract.toJSON();

// Create a new contract from the round-tripped JSON
const contract2 = sdk.DataContract.fromJSON(roundTripped, PLATFORM_VERSION_CONTRACT_V1);
const contract2 = sdk.DataContract.fromJSON(roundTripped, FORMATS.V1.platformVersion);
const roundTripped2 = contract2.toJSON();

expect(roundTripped2).to.deep.equal(roundTripped);
Expand All @@ -230,8 +262,8 @@ describe('DataContract', () => {

describe('Memory Management', () => {
it('should handle memory management properly with multiple contracts', async () => {
const contract1 = sdk.DataContract.fromJSON(contractFixtureV0, PLATFORM_VERSION_CONTRACT_V0);
const contract2 = sdk.DataContract.fromJSON(contractFixtureV1, PLATFORM_VERSION_CONTRACT_V1);
const contract1 = sdk.DataContract.fromJSON(contractFixtureV0, FORMATS.V0.platformVersion);
const contract2 = sdk.DataContract.fromJSON(contractFixtureV1, FORMATS.V1.platformVersion);

expect(contract1.id()).to.equal(contractFixtureV0.id);
expect(contract2.id()).to.equal(contractFixtureV1.id);
Expand All @@ -242,12 +274,16 @@ describe('DataContract', () => {
});

describe('Platform Version Compatibility Matrix', () => {
describe('V0 Contract Compatibility', () => {
testContractAcrossVersions(contractFixtureV0, 'V0', V0_COMPATIBLE_VERSIONS);
});

describe('V1 Contract Compatibility', () => {
testContractAcrossVersions(contractFixtureV1, 'V1', V1_COMPATIBLE_VERSIONS, V0_ONLY_VERSIONS);
// Dynamically test all defined contract formats
Object.entries(FORMATS).forEach(([formatKey, formatData]) => {
describe(`${formatKey} Contract Compatibility`, () => {
testContractAcrossVersions(
formatData.fixture,
formatKey,
formatData.compatibleVersions,
formatData.incompatibleVersions
);
});
});

describe('Edge Cases', () => {
Expand Down