@patchworkdev/pdk
Version:
Patchwork Development Kit
389 lines (388 loc) • 18.3 kB
JavaScript
"use strict";
// import fs from 'fs';
// import path from 'path';
// import { CLIProcessor } from './cliProcessor';
// const CONTRACT_SCHEMA = path.join(__dirname, '../../../../../schemas/patchwork-contract-config.schema.json');
// const PROJECT_SCHEMA = path.join(__dirname, '../../../../../schemas/patchwork-project-config.schema.json');
// const cliProcessor = new CLIProcessor(CONTRACT_SCHEMA, PROJECT_SCHEMA);
Object.defineProperty(exports, "__esModule", { value: true });
const project_1 = require("../helpers/project");
//TODO: Uncomment adn add back commented out tests.
describe('CLI', () => {
// Placeholder test to keep Jest happy while other tests are commented out
test('placeholder - remove when real tests are uncommented', () => {
expect(true).toBe(true);
});
// const testDataDir = path.resolve(__dirname, '../../../../common/src/codegen/test_data');
// const projectConfigsDir = path.join(testDataDir, 'project_configs');
// const outputDir = path.resolve(__dirname, '../test_output');
// beforeAll(() => {
// // Ensure the output directory exists
// if (!fs.existsSync(outputDir)) {
// fs.mkdirSync(outputDir, { recursive: true });
// }
// });
// afterAll(() => {
// // Clean up the output directory after tests
// if (fs.existsSync(outputDir)) {
// fs.rmSync(outputDir, { recursive: true });
// }
// });
// test('validate command with valid contract config', () => {
// const configFile = path.join(testDataDir, 'Arrays.json');
// const result = cliProcessor.validateConfig(configFile);
// expect(result).toBe(true);
// });
// test('validate command with valid project config', () => {
// const configFile = path.join(projectConfigsDir, 'project-config.json');
// const result = cliProcessor.validateConfig(configFile);
// expect(result).toBe(true);
// });
// test('validate command with invalid config', () => {
// const configFile = path.join(testDataDir, 'Arrays-schema.json');
// const result = cliProcessor.validateConfig(configFile);
// expect(result).toBe(false);
// });
// test('generate command with contract config', () => {
// const configFile = path.join(testDataDir, 'Arrays.json');
// cliProcessor.generateSolidity([configFile], outputDir);
// const generatedFiles = fs.readdirSync(outputDir);
// expect(generatedFiles).toContain('ArraysGenerated.sol');
// expect(generatedFiles).toContain('Arrays.sol');
// expect(generatedFiles).toContain('Arrays-schema.json');
// });
// test('generate command with project config', () => {
// const configFile = path.join(projectConfigsDir, 'project-config-contract-config.json');
// cliProcessor.generateSolidity([configFile], outputDir);
// const generatedFiles = fs.readdirSync(outputDir);
// expect(generatedFiles.length).toBeGreaterThan(0);
// // Check for specific files that should be generated based on project-config-contract-config.json
// expect(generatedFiles).toContain('AccountPatchGenerated.sol');
// expect(generatedFiles).toContain('AccountPatch.sol');
// expect(generatedFiles).toContain('AccountPatch-schema.json');
// expect(generatedFiles).toContain('SecondContractGenerated.sol');
// expect(generatedFiles).toContain('SecondContract.sol');
// expect(generatedFiles).toContain('SecondContract-schema.json');
// });
});
describe('ProjectConfig validation', () => {
test('placeholder - remove when real tests are uncommented', () => {
expect(true).toBe(true);
});
it('throws an error when a contract field key is exactly the reserved word "metadata"', () => {
const invalidProjectConfig = {
name: 'invalidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
MyContract: {
scopeName: 'default',
name: 'MyContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [{ id: 1, key: 'metadata', type: 'uint256', description: 'Test field' }],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid field key "metadata" in contract "MyContract": field keys cannot be exactly the reserved word "metadata"');
});
it('throws an error when there are duplicate field keys', () => {
const invalidProjectConfig = {
name: 'invalidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
MyContract: {
scopeName: 'default',
name: 'MyContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [
{ id: 0, key: 'fieldA', type: 'uint256', description: 'Field A' },
{ id: 1, key: 'fieldA', type: 'uint256', description: 'Duplicate Field A' },
],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow(/Duplicate field keys/);
});
// New tests for alphanumeric validation
it('throws an error when project name contains non-alphanumeric characters', () => {
const invalidProjectConfig = {
name: 'invalid-project',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid project name "invalid-project": project name must contain only alphanumeric characters');
});
it('throws an error when contract name contains non-alphanumeric characters', () => {
const invalidProjectConfig = {
name: 'ValidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
'My-Contract': {
scopeName: 'default',
name: 'My-Contract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid contract name "My-Contract": contract name must contain only alphanumeric characters');
});
// New tests for contract key matching
it('throws an error when contract key does not match contract name', () => {
const invalidProjectConfig = {
name: 'ValidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
ContractOne: {
scopeName: 'default',
name: 'DifferentName',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Contract key mismatch: the key "ContractOne" must match the contract name "DifferentName"');
});
// New tests for contract reference validation
it('throws an error when banker references non-existent contract', () => {
const invalidProjectConfig = {
name: 'ValidProject',
scopes: [
{
name: 'default',
whitelist: true,
userAssign: true,
userPatch: true,
bankers: ['0x1234567890123456789012345678901234567890', 'NonExistentContract'],
},
],
contracts: {
ExistingContract: {
scopeName: 'default',
name: 'ExistingContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid banker reference "NonExistentContract" in scope "default": must be an Ethereum address or a valid contract key');
});
it('throws an error when operator references non-existent contract', () => {
const invalidProjectConfig = {
name: 'ValidProject',
scopes: [
{
name: 'default',
whitelist: true,
userAssign: true,
userPatch: true,
operators: ['0x1234567890123456789012345678901234567890', 'NonExistentContract'],
},
],
contracts: {
ExistingContract: {
scopeName: 'default',
name: 'ExistingContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid operator reference "NonExistentContract" in scope "default": must be an Ethereum address or a valid contract key');
});
it('throws an error when fragment references non-existent contract', () => {
const invalidProjectConfig = {
name: 'ValidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
ExistingContract: {
scopeName: 'default',
name: 'ExistingContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: ['NonExistentContract'],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow('Invalid fragment reference "NonExistentContract" in contract "ExistingContract": must be a valid contract key');
});
it('accepts valid Ethereum addresses in bankers and operators', () => {
const validProjectConfig = {
name: 'ValidProject',
scopes: [
{
name: 'default',
whitelist: true,
userAssign: true,
userPatch: true,
bankers: ['0x1234567890123456789012345678901234567890', 'ExistingContract'],
operators: ['0x0987654321098765432109876543210987654321', 'ExistingContract'],
},
],
contracts: {
ExistingContract: {
scopeName: 'default',
name: 'ExistingContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(validProjectConfig)).not.toThrow();
});
});
describe('ProjectConfig field ID validation', () => {
it('passes when there are no duplicate field ids, regardless of order or gaps', () => {
// Valid config: field ids are 2, 5, 9. They are not sequential,
// but that's acceptable since we only require uniqueness.
const validProjectConfig = {
name: 'validProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
MyContract: {
scopeName: 'default',
name: 'MyContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [
{ id: 2, key: 'fieldC', type: 'uint256', description: 'Field C' },
{ id: 5, key: 'fieldA', type: 'uint256', description: 'Field A' },
{ id: 9, key: 'fieldB', type: 'uint256', description: 'Field B' },
],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(validProjectConfig)).not.toThrow();
});
it('throws an error if there are duplicate field ids', () => {
const invalidProjectConfig = {
name: 'invalidProject',
scopes: [{ name: 'default', whitelist: true, userAssign: true, userPatch: true }],
contracts: {
MyContract: {
scopeName: 'default',
name: 'MyContract',
symbol: 'MYC',
baseURI: 'https://example.com/base',
schemaURI: 'https://example.com/schema',
imageURI: 'https://example.com/image',
fields: [
{ id: 0, key: 'fieldA', type: 'uint256', description: 'Field A' },
{ id: 0, key: 'fieldB', type: 'uint256', description: 'Field B' },
],
features: [],
fragments: [],
},
},
networks: {
local: { chain: 'anvil', rpc: 'http://localhost' },
testnet: { chain: 'anvil', rpc: 'http://localhost' },
mainnet: { chain: 'anvil', rpc: 'http://localhost' },
},
plugins: [],
};
expect(() => (0, project_1.importProjectConfig)(invalidProjectConfig)).toThrow(/Duplicate field IDs/);
});
});