Skip to main content
Glama

firewalla-mcp-server

test-rule-pause-resume-api.jsโ€ข12.9 kB
#!/usr/bin/env node /** * Comprehensive test script to investigate Firewalla rule pause/resume API behavior * Tests three different documented approaches to determine what actually works */ import axios from 'axios'; import dotenv from 'dotenv'; import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load environment variables dotenv.config({ path: join(__dirname, '..', '.env') }); const MSP_TOKEN = process.env.FIREWALLA_MSP_TOKEN; const MSP_DOMAIN = process.env.FIREWALLA_MSP_ID; const BOX_ID = process.env.FIREWALLA_BOX_ID; if (!MSP_TOKEN || !MSP_DOMAIN || !BOX_ID) { console.error('โŒ Missing required environment variables'); console.error('Required: FIREWALLA_MSP_TOKEN, FIREWALLA_MSP_ID, FIREWALLA_BOX_ID'); process.exit(1); } const BASE_URL = `https://${MSP_DOMAIN}/v2`; // Configure axios instance with same headers as production client const api = axios.create({ baseURL: BASE_URL, headers: { 'Authorization': `Bearer ${MSP_TOKEN}`, 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'en-US,en;q=0.9', 'Accept-Encoding': 'gzip, deflate, br', 'Connection': 'keep-alive', 'Sec-Fetch-Dest': 'empty', 'Sec-Fetch-Mode': 'cors', 'Sec-Fetch-Site': 'same-origin' } }); // Add request/response logging api.interceptors.request.use(config => { console.log(`\n๐Ÿ“ค ${config.method?.toUpperCase()} ${config.url}`); if (config.data) { console.log('Request Body:', JSON.stringify(config.data, null, 2)); } return config; }); api.interceptors.response.use( response => { console.log(`โœ… Status: ${response.status}`); // Check if response is HTML instead of JSON const contentType = response.headers['content-type']; if (contentType && contentType.includes('text/html')) { console.log('โš ๏ธ WARNING: Received HTML response instead of JSON!'); console.log('Content-Type:', contentType); console.log('Response preview:', response.data.substring(0, 200) + '...'); // Check for common redirect/auth issues if (response.data.includes('login') || response.data.includes('sign-in')) { console.log('๐Ÿ” Appears to be a login page - authentication may have failed'); } } else { console.log('Response:', JSON.stringify(response.data, null, 2)); } return response; }, error => { console.log(`โŒ Error: ${error.response?.status || 'Network Error'}`); if (error.response?.data) { const contentType = error.response.headers['content-type']; if (contentType && contentType.includes('text/html')) { console.log('โš ๏ธ HTML Error Response - Preview:', error.response.data.substring(0, 200) + '...'); } else { console.log('Error Response:', JSON.stringify(error.response.data, null, 2)); } } else { console.log('Error:', error.message); } return Promise.reject(error); } ); // Test approaches const TestApproaches = { // Approach 1: Official documentation - POST with no body OFFICIAL: { name: 'Official Docs Approach', pause: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/pause`, data: null }, resume: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/resume`, data: null } }, // Approach 2: Our implementation - POST with duration and box OUR_IMPLEMENTATION: { name: 'Our Implementation', pause: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/pause`, data: { duration: 60, box: BOX_ID } }, resume: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/resume`, data: { box: BOX_ID } } }, // Approach 3: Third-party research - PATCH with status THIRD_PARTY: { name: 'Third Party PATCH Approach', pause: { method: 'PATCH', url: (ruleId) => `/rules/${ruleId}`, data: { status: 'paused', resumeTs: Date.now() + (60 * 60 * 1000) // 1 hour from now } }, resume: { method: 'PATCH', url: (ruleId) => `/rules/${ruleId}`, data: { status: 'active' } } }, // Additional variations to test VARIATIONS: [ { name: 'POST with box only (no duration)', pause: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/pause`, data: { box: BOX_ID } } }, { name: 'POST with duration only (no box)', pause: { method: 'POST', url: (ruleId) => `/rules/${ruleId}/pause`, data: { duration: 60 } } }, { name: 'Box-specific endpoint with POST', pause: { method: 'POST', url: (ruleId) => `/boxes/${BOX_ID}/rules/${ruleId}/pause`, data: null } }, { name: 'Box-specific endpoint with duration', pause: { method: 'POST', url: (ruleId) => `/boxes/${BOX_ID}/rules/${ruleId}/pause`, data: { duration: 60 } } } ] }; // Helper functions async function getExistingRules() { console.log('\n๐Ÿ” Fetching existing rules...'); try { const response = await api.get(`/boxes/${BOX_ID}/rules`, { params: { limit: 10 } }); return response.data.results || []; } catch (error) { console.error('Failed to fetch rules:', error.message); return []; } } async function getRuleDetails(ruleId) { try { const response = await api.get(`/boxes/${BOX_ID}/rules/${ruleId}`); return response.data; } catch (error) { console.error(`Failed to get rule details for ${ruleId}:`, error.message); return null; } } async function createTestRule() { console.log('\n๐Ÿ”จ Creating test rule...'); try { const response = await api.post(`/boxes/${BOX_ID}/rules`, { action: 'block', target: 'domain', target_value: 'test-pause-resume.example.com', name: 'Test Rule for Pause/Resume API Investigation', enabled: true }); // Check if we got JSON response if (typeof response.data === 'object' && response.data.id) { console.log('โœ… Created test rule:', response.data.id); return response.data; } else { console.error('โŒ Failed to create rule - unexpected response format'); console.log('Response type:', typeof response.data); return null; } } catch (error) { console.error('Failed to create test rule:', error.message); return null; } } async function deleteTestRule(ruleId) { try { await api.delete(`/boxes/${BOX_ID}/rules/${ruleId}`); console.log('๐Ÿ—‘๏ธ Deleted test rule:', ruleId); } catch (error) { console.error('Failed to delete test rule:', error.message); } } async function testApproach(approach, ruleId, operation = 'pause') { console.log(`\n๐Ÿงช Testing ${approach.name} - ${operation.toUpperCase()}`); console.log('โ”'.repeat(60)); const config = approach[operation]; if (!config) { console.log('โš ๏ธ No configuration for this operation'); return { success: false, error: 'No configuration' }; } try { const url = typeof config.url === 'function' ? config.url(ruleId) : config.url; const response = await api({ method: config.method, url: url, data: config.data }); // Check if the operation actually worked await new Promise(resolve => setTimeout(resolve, 1000)); // Wait a bit const ruleDetails = await getRuleDetails(ruleId); return { success: true, response: response.data, status: response.status, ruleState: ruleDetails?.status || ruleDetails?.state || 'unknown', actuallyPaused: ruleDetails?.status === 'paused' || ruleDetails?.state === 'paused' }; } catch (error) { return { success: false, error: error.message, status: error.response?.status, errorData: error.response?.data }; } } async function runComprehensiveTests() { console.log('๐Ÿš€ Starting Comprehensive Rule Pause/Resume API Tests'); console.log('โ•'.repeat(60)); console.log(`MSP Domain: ${MSP_DOMAIN}`); console.log(`Box ID: ${BOX_ID}`); console.log('โ•'.repeat(60)); // Get or create a test rule let testRule = null; const existingRules = await getExistingRules(); // Look for an existing test rule const existingTestRule = existingRules.find(r => r.name?.includes('Test Rule for Pause/Resume') || r.target_value === 'test-pause-resume.example.com' ); if (existingTestRule) { console.log('โœ… Found existing test rule:', existingTestRule.id); testRule = existingTestRule; // Ensure it's resumed before testing await testApproach(TestApproaches.OUR_IMPLEMENTATION, testRule.id, 'resume'); } else { testRule = await createTestRule(); if (!testRule) { console.error('โŒ Failed to create test rule. Exiting.'); return; } } const results = { pause: {}, resume: {}, variations: [] }; // Test main approaches - PAUSE console.log('\n\n๐Ÿ“‹ TESTING PAUSE OPERATIONS'); console.log('โ•'.repeat(60)); for (const [key, approach] of Object.entries(TestApproaches)) { if (key === 'VARIATIONS') continue; results.pause[key] = await testApproach(approach, testRule.id, 'pause'); // If pause was successful, test resume if (results.pause[key].success && results.pause[key].actuallyPaused) { console.log('\n๐Ÿ”„ Rule was paused, testing resume...'); results.resume[key] = await testApproach(approach, testRule.id, 'resume'); } // Ensure rule is active for next test await new Promise(resolve => setTimeout(resolve, 2000)); } // Test variations console.log('\n\n๐Ÿ“‹ TESTING VARIATIONS'); console.log('โ•'.repeat(60)); for (const variation of TestApproaches.VARIATIONS) { const result = await testApproach(variation, testRule.id, 'pause'); results.variations.push({ name: variation.name, result }); // Ensure rule is active for next test if (result.success && result.actuallyPaused) { await testApproach(TestApproaches.OUR_IMPLEMENTATION, testRule.id, 'resume'); await new Promise(resolve => setTimeout(resolve, 1000)); } } // Summary Report console.log('\n\n๐Ÿ“Š TEST RESULTS SUMMARY'); console.log('โ•'.repeat(60)); console.log('\n๐Ÿ”ธ PAUSE OPERATIONS:'); for (const [key, result] of Object.entries(results.pause)) { const approach = TestApproaches[key]; console.log(`\n${approach.name}:`); console.log(` Success: ${result.success ? 'โœ…' : 'โŒ'}`); console.log(` Actually Paused: ${result.actuallyPaused ? 'โœ…' : 'โŒ'}`); if (result.error) { console.log(` Error: ${result.error}`); } if (result.response) { console.log(` Response preview:`, JSON.stringify(result.response).substring(0, 100)); } } console.log('\n\n๐Ÿ”ธ RESUME OPERATIONS:'); for (const [key, result] of Object.entries(results.resume)) { const approach = TestApproaches[key]; console.log(`\n${approach.name}:`); console.log(` Success: ${result.success ? 'โœ…' : 'โŒ'}`); if (result.error) { console.log(` Error: ${result.error}`); } } console.log('\n\n๐Ÿ”ธ VARIATIONS:'); for (const variation of results.variations) { console.log(`\n${variation.name}:`); console.log(` Success: ${variation.result.success ? 'โœ…' : 'โŒ'}`); console.log(` Actually Paused: ${variation.result.actuallyPaused ? 'โœ…' : 'โŒ'}`); if (variation.result.error) { console.log(` Error: ${variation.result.error}`); } } // Analysis console.log('\n\n๐Ÿ” ANALYSIS:'); console.log('โ”'.repeat(60)); // Find what worked const workingPauseApproaches = Object.entries(results.pause) .filter(([_, result]) => result.success && result.actuallyPaused) .map(([key, _]) => TestApproaches[key].name); const workingVariations = results.variations .filter(v => v.result.success && v.result.actuallyPaused) .map(v => v.name); console.log('\nโœ… Working Pause Approaches:'); if (workingPauseApproaches.length > 0) { workingPauseApproaches.forEach(name => console.log(` - ${name}`)); } else { console.log(' None of the main approaches worked!'); } if (workingVariations.length > 0) { console.log('\nโœ… Working Variations:'); workingVariations.forEach(name => console.log(` - ${name}`)); } // Clean up if (testRule && !existingTestRule) { console.log('\n\n๐Ÿงน Cleaning up...'); await deleteTestRule(testRule.id); } console.log('\n\nโœ… Test complete!'); } // Run the tests runComprehensiveTests().catch(error => { console.error('Fatal error:', error); process.exit(1); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/amittell/firewalla-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server