@semantest/chrome-extension
Version:
Browser extension for ChatGPT-buddy - AI automation extension built on Web-Buddy framework
503 lines (440 loc) • 15.4 kB
JavaScript
/**
* @fileoverview Contract Integration Tests
* @description Tests for contract discovery and execution in Web-Buddy extension
*/
// Mock Chrome APIs
global.chrome = {
runtime: {
onMessage: {
addListener: jest.fn()
},
sendMessage: jest.fn(),
id: 'test-extension-id'
},
tabs: {
query: jest.fn(),
sendMessage: jest.fn()
}
};
// Mock WebSocket
global.WebSocket = jest.fn().mockImplementation(() => ({
onopen: null,
onmessage: null,
onclose: null,
onerror: null,
send: jest.fn(),
close: jest.fn()
}));
// Mock DOM for contract discovery
const mockDocument = {
querySelectorAll: jest.fn(),
querySelector: jest.fn(),
body: {
classList: [],
children: { length: 5 }
},
title: 'Demo E-commerce Store',
addEventListener: jest.fn(),
dispatchEvent: jest.fn()
};
const mockWindow = {
location: {
hostname: 'demo-store.example.com',
href: 'https://demo-store.example.com/',
pathname: '/'
},
addEventListener: jest.fn(),
dispatchEvent: jest.fn(),
setInterval: jest.fn(),
clearInterval: jest.fn(),
setTimeout: jest.fn(),
Date: Date
};
// Set up global mocks
global.document = mockDocument;
global.window = mockWindow;
describe('Contract Discovery and Integration', () => {
let contractDiscovery;
let contractExecution;
beforeEach(() => {
jest.clearAllMocks();
// Reset DOM mocks
mockDocument.querySelectorAll.mockReturnValue([]);
mockDocument.querySelector.mockReturnValue(null);
});
describe('Contract Discovery', () => {
test('should discover contracts from meta tags', () => {
// Mock meta tag with contract
const mockMetaTag = {
getAttribute: jest.fn().mockReturnValue(JSON.stringify({
version: '1.0.0',
domain: 'demo-store.example.com',
title: 'E-commerce Contract',
capabilities: {
searchProducts: {
type: 'form',
description: 'Search for products',
selector: '[data-testid="search-input"]'
}
}
}))
};
mockDocument.querySelectorAll.mockImplementation((selector) => {
if (selector === 'meta[name^="web-buddy-contract"]') {
return [mockMetaTag];
}
return [];
});
// Import and test contract discovery
const { ContractDiscoveryAdapter } = require('../src/contracts/contract-discovery-adapter');
contractDiscovery = new ContractDiscoveryAdapter();
const result = contractDiscovery.discoverFromMetaTags();
expect(result).toHaveLength(1);
expect(result[0].domain).toBe('demo-store.example.com');
expect(result[0].title).toBe('E-commerce Contract');
expect(result[0].capabilities.searchProducts).toBeDefined();
});
test('should discover contracts from JSON-LD scripts', () => {
// Mock JSON-LD script with contract
const mockScript = {
textContent: JSON.stringify({
'@type': 'WebBuddyContract',
version: '1.0.0',
domain: 'demo-store.example.com',
title: 'JSON-LD Contract',
capabilities: {
addToCart: {
type: 'action',
description: 'Add product to cart',
selector: '.add-to-cart-btn'
}
}
})
};
mockDocument.querySelectorAll.mockImplementation((selector) => {
if (selector === 'script[type="application/ld+json"]') {
return [mockScript];
}
return [];
});
const { ContractDiscoveryAdapter } = require('../src/contracts/contract-discovery-adapter');
contractDiscovery = new ContractDiscoveryAdapter();
const result = contractDiscovery.discoverFromJsonLD();
expect(result).toHaveLength(1);
expect(result[0]['@type']).toBe('WebBuddyContract');
expect(result[0].capabilities.addToCart).toBeDefined();
});
test('should handle invalid contract data gracefully', () => {
// Mock meta tag with invalid JSON
const mockMetaTag = {
getAttribute: jest.fn().mockReturnValue('invalid json content')
};
mockDocument.querySelectorAll.mockImplementation((selector) => {
if (selector === 'meta[name^="web-buddy-contract"]') {
return [mockMetaTag];
}
return [];
});
const { ContractDiscoveryAdapter } = require('../src/contracts/contract-discovery-adapter');
contractDiscovery = new ContractDiscoveryAdapter();
// Should not throw and return empty array
const result = contractDiscovery.discoverFromMetaTags();
expect(result).toHaveLength(0);
});
});
describe('Contract Execution', () => {
test('should execute contract capabilities successfully', async () => {
// Mock DOM element for execution
const mockElement = {
click: jest.fn(),
tagName: 'BUTTON',
getAttribute: jest.fn(),
value: '',
dispatchEvent: jest.fn()
};
mockDocument.querySelector.mockReturnValue(mockElement);
const { ContractExecutionService } = require('../src/contracts/contract-execution-service');
contractExecution = new ContractExecutionService();
// Mock contract discovery to return a test contract
const mockContract = {
domain: 'demo-store.example.com',
title: 'Test Contract',
capabilities: {
clickButton: {
type: 'action',
description: 'Click a button',
selector: '[data-testid="test-button"]'
}
}
};
// Mock the discovery adapter
contractExecution.discoveryAdapter = {
getContractsByDomain: jest.fn().mockReturnValue([mockContract]),
executeCapability: jest.fn().mockResolvedValue({
success: true,
action: 'click',
element: 'BUTTON'
})
};
const result = await contractExecution.executeWithContract({
action: 'clickButton',
parameters: {},
domain: 'demo-store.example.com'
});
expect(result.success).toBe(true);
expect(result.data.action).toBe('click');
});
test('should validate execution parameters correctly', () => {
const { ContractExecutionService } = require('../src/contracts/contract-execution-service');
contractExecution = new ContractExecutionService();
// Mock contract with required parameters
const mockContract = {
domain: 'demo-store.example.com',
title: 'Test Contract',
capabilities: {
fillForm: {
type: 'form',
description: 'Fill a form',
selector: '[data-testid="form"]',
parameters: [
{
name: 'username',
type: 'string',
required: true
},
{
name: 'email',
type: 'string',
required: true
}
]
}
}
};
contractExecution.discoveryAdapter = {
getContractsByDomain: jest.fn().mockReturnValue([mockContract])
};
// Test with missing required parameter
const invalidResult = contractExecution.validateExecutionParams('fillForm', {
username: 'testuser'
// missing email
});
expect(invalidResult.valid).toBe(false);
expect(invalidResult.errors).toContain('Required parameter missing: email');
// Test with all required parameters
const validResult = contractExecution.validateExecutionParams('fillForm', {
username: 'testuser',
email: 'test@example.com'
});
expect(validResult.valid).toBe(true);
expect(validResult.errors).toHaveLength(0);
});
test('should provide contract availability information', async () => {
const { ContractExecutionService } = require('../src/contracts/contract-execution-service');
contractExecution = new ContractExecutionService();
const mockContracts = [
{
domain: 'demo-store.example.com',
title: 'E-commerce Contract',
capabilities: {
search: { type: 'form', description: 'Search products' },
addToCart: { type: 'action', description: 'Add to cart' }
}
}
];
contractExecution.discoveryAdapter = {
getContractsByDomain: jest.fn().mockReturnValue(mockContracts)
};
const availability = await contractExecution.checkContractAvailability();
expect(availability.available).toBe(true);
expect(availability.contractCount).toBe(1);
expect(availability.capabilities).toContain('search');
expect(availability.capabilities).toContain('addToCart');
expect(availability.domain).toBe('demo-store.example.com');
});
});
describe('Message Handling Integration', () => {
test('should handle contract execution messages', async () => {
// Mock content script with contract handlers
const mockSendResponse = jest.fn();
// Simulate contract execution message
const message = {
type: 'contractExecutionRequested',
correlationId: 'test-123',
payload: {
action: 'searchProducts',
parameters: { query: 'laptop' }
}
};
// Mock successful contract execution
const expectedResponse = {
correlationId: 'test-123',
status: 'success',
data: {
success: true,
action: 'search',
query: 'laptop'
},
executionTime: 150,
contractId: 'demo-store.example.com_contract_123',
timestamp: expect.any(String)
};
// Verify message structure
expect(message.type).toBe('contractExecutionRequested');
expect(message.payload.action).toBe('searchProducts');
expect(message.payload.parameters.query).toBe('laptop');
});
test('should handle contract discovery messages', async () => {
const message = {
type: 'contractDiscoveryRequested',
correlationId: 'discovery-456'
};
const expectedResponse = {
correlationId: 'discovery-456',
status: 'success',
data: {
contracts: expect.any(Array),
availability: expect.objectContaining({
available: expect.any(Boolean),
contractCount: expect.any(Number)
}),
availableActions: expect.any(Array),
url: expect.any(String),
domain: expect.any(String)
},
timestamp: expect.any(String)
};
// Verify message structure
expect(message.type).toBe('contractDiscoveryRequested');
expect(message.correlationId).toBe('discovery-456');
});
});
describe('Background Script Integration', () => {
test('should register contract handlers in message dispatcher', () => {
// Mock the message dispatcher registration
const mockHandlers = new Map();
const mockDispatcher = {
handlers: mockHandlers,
registerHandler: jest.fn((type, handler) => {
mockHandlers.set(type, handler);
})
};
// Simulate handler registration
mockDispatcher.registerHandler('ContractExecutionRequested', {});
mockDispatcher.registerHandler('ContractDiscoveryRequested', {});
mockDispatcher.registerHandler('ContractAvailabilityCheck', {});
expect(mockHandlers.has('ContractExecutionRequested')).toBe(true);
expect(mockHandlers.has('ContractDiscoveryRequested')).toBe(true);
expect(mockHandlers.has('ContractAvailabilityCheck')).toBe(true);
});
test('should forward contract messages to content script', async () => {
const mockTab = { id: 123 };
chrome.tabs.query.mockResolvedValue([mockTab]);
const mockMessage = {
type: 'ContractExecutionRequested',
correlationId: 'test-789',
payload: { action: 'test' }
};
// Simulate message forwarding
chrome.tabs.sendMessage.mockImplementation((tabId, message, callback) => {
expect(tabId).toBe(123);
expect(message.type).toBe('contractExecutionRequested');
callback({ status: 'success' });
});
// This would be called by the actual handler
chrome.tabs.sendMessage(mockTab.id, {
...mockMessage,
type: 'contractExecutionRequested'
}, (response) => {
expect(response.status).toBe('success');
});
expect(chrome.tabs.sendMessage).toHaveBeenCalledWith(
123,
expect.objectContaining({
type: 'contractExecutionRequested',
correlationId: 'test-789'
}),
expect.any(Function)
);
});
});
});
// Integration test for the complete contract flow
describe('End-to-End Contract Integration', () => {
test('should complete full contract discovery and execution flow', async () => {
// 1. Contract Discovery Phase
const discoveryMessage = {
type: 'contractDiscoveryRequested',
correlationId: 'e2e-discovery'
};
// Mock discovered contract
const discoveredContract = {
version: '1.0.0',
domain: 'demo-store.example.com',
title: 'E-commerce Demo Contract',
capabilities: {
searchProducts: {
type: 'form',
description: 'Search for products',
selector: '[data-testid="search-input"]',
parameters: [
{ name: 'query', type: 'string', required: true }
]
}
}
};
// 2. Contract Execution Phase
const executionMessage = {
type: 'contractExecutionRequested',
correlationId: 'e2e-execution',
payload: {
action: 'searchProducts',
parameters: { query: 'gaming laptop' }
}
};
// 3. Verify contract availability
const availabilityMessage = {
type: 'contractAvailabilityCheck',
correlationId: 'e2e-availability'
};
// Simulate the complete flow
const flow = {
discovery: {
request: discoveryMessage,
response: {
correlationId: 'e2e-discovery',
status: 'success',
data: {
contracts: [discoveredContract],
availability: {
available: true,
contractCount: 1,
capabilities: ['searchProducts'],
domain: 'demo-store.example.com'
}
}
}
},
execution: {
request: executionMessage,
response: {
correlationId: 'e2e-execution',
status: 'success',
data: {
success: true,
action: 'search',
query: 'gaming laptop'
},
contractId: 'demo-store.example.com_contract',
capabilityName: 'searchProducts'
}
}
};
// Verify the flow structure
expect(flow.discovery.request.type).toBe('contractDiscoveryRequested');
expect(flow.discovery.response.data.contracts).toHaveLength(1);
expect(flow.execution.request.payload.action).toBe('searchProducts');
expect(flow.execution.response.status).toBe('success');
console.log('✅ End-to-end contract integration test completed successfully');
});
});