refactor: streamline provider testing utility and remove deprecated test configurations

This commit is contained in:
Himanshu
2025-12-14 15:05:23 +05:30
parent 5238f7be2c
commit 28eeb51898
3 changed files with 588 additions and 300 deletions

View File

@@ -1,188 +1,590 @@
const axios = require("axios");
const cheerio = require("cheerio");
const fs = require("fs");
const path = require("path");
// Load extractors and utilities
let providerContext;
try {
const { getBaseUrl } = require("./dist/getBaseUrl.js");
const { hubcloudExtracter } = require("./dist/hubcloudExtractor.js");
const { gofileExtracter } = require("./dist/gofileExtracter.js");
const { superVideoExtractor } = require("./dist/superVideoExtractor.js");
const { gdFlixExtracter } = require("./dist/gdFlixExtractor.js");
providerContext = {
axios,
cheerio,
getBaseUrl,
commonHeaders: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
},
extractors: {
hubcloudExtracter,
gofileExtracter,
superVideoExtractor,
gdFlixExtracter,
},
Crypto: {},
};
} catch (error) {
console.log(
"⚠️ Could not load provider context. Run 'npm run build' first."
);
providerContext = null;
}
/**
* Provider testing utility
* Helper to pick random items from array
*/
function pickRandom(arr, count = 1) {
const shuffled = [...arr].sort(() => Math.random() - 0.5);
return count === 1 ? shuffled[0] : shuffled.slice(0, count);
}
/**
* Sleep helper
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* Provider testing utility - Full integration test
*/
class ProviderTester {
constructor(serverUrl = "http://localhost:3001") {
this.serverUrl = serverUrl;
this.axios = axios.create({
baseURL: serverUrl,
timeout: 10000,
});
constructor(options = {}) {
this.timeout = options.timeout || 30000;
this.postsToTest = options.postsToTest || 2;
this.linksToTest = options.linksToTest || 2;
this.signal = new AbortController().signal;
this.results = {};
}
/**
* Test server connectivity
* Load provider module
*/
async testConnection() {
loadModule(providerName, moduleName) {
try {
const response = await this.axios.get("/health");
console.log("✅ Server connection OK");
return true;
const modulePath = `./dist/${providerName}/${moduleName}.js`;
// Clear cache to get fresh module
delete require.cache[require.resolve(modulePath)];
return require(modulePath);
} catch (error) {
console.error("❌ Server connection failed:", error.message);
return false;
}
}
/**
* Test manifest endpoint
*/
async testManifest() {
try {
const response = await this.axios.get("/manifest.json");
const providers = response.data;
console.log(`✅ Manifest OK - Found ${providers.length} providers:`);
providers.forEach((p) => {
console.log(` 📦 ${p.display_name} (${p.value}) v${p.version}`);
});
return providers;
} catch (error) {
console.error("❌ Manifest test failed:", error.message);
return null;
}
}
/**
* Test individual provider modules
* Load manifest to get enabled providers
*/
loadManifest() {
try {
const manifestPath = path.join(__dirname, "manifest.json");
if (!fs.existsSync(manifestPath)) {
console.log("⚠️ manifest.json not found");
return [];
}
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
return manifest;
} catch (error) {
console.log("⚠️ Failed to load manifest:", error.message);
return [];
}
}
/**
* Get available providers from dist folder (excluding disabled ones)
*/
getAvailableProviders() {
const distPath = path.join(__dirname, "dist");
if (!fs.existsSync(distPath)) {
console.log("❌ dist folder not found. Run 'npm run build' first.");
return [];
}
// Load manifest to check for disabled providers
const manifest = this.loadManifest();
const disabledProviders = manifest
.filter((p) => p.disabled === true)
.map((p) => p.value);
if (disabledProviders.length > 0) {
console.log(
`\n⏭️ Skipping disabled providers: ${disabledProviders.join(", ")}`
);
}
const providers = fs
.readdirSync(distPath, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
.filter((name) => {
// Skip disabled providers
if (disabledProviders.includes(name)) {
return false;
}
// Check if it has required modules
const hasRequired = [
"catalog.js",
"posts.js",
"meta.js",
"stream.js",
].every((file) => fs.existsSync(path.join(distPath, name, file)));
return hasRequired;
});
return providers;
}
/**
* Test a single provider with full flow
*/
async testProvider(providerName) {
console.log(`\n🧪 Testing provider: ${providerName}`);
console.log(`\n${"=".repeat(60)}`);
console.log(`🧪 Testing Provider: ${providerName}`);
console.log("=".repeat(60));
const modules = ["catalog", "posts", "meta", "stream", "episodes"];
const results = {};
const result = {
provider: providerName,
catalog: { success: false, data: null, error: null },
posts: { success: false, data: null, error: null },
meta: { success: false, data: null, error: null },
episodes: { success: false, data: null, error: null, skipped: false },
stream: { success: false, data: null, error: null, skipped: false },
summary: { passed: 0, failed: 0, skipped: 0 },
};
for (const module of modules) {
try {
const response = await this.axios.get(
`/dist/${providerName}/${module}.js`
try {
// Step 1: Load and test catalog
console.log("\n📂 Step 1: Loading Catalog...");
const catalogModule = this.loadModule(providerName, "catalog");
if (!catalogModule) {
throw new Error("Catalog module not found");
}
const catalog = catalogModule.catalog || [];
// const genres = catalogModule.genres || [];
const allFilters = [...catalog];
if (allFilters.length === 0) {
throw new Error("No filters found in catalog");
}
result.catalog.success = true;
result.catalog.data = {
catalogCount: catalog.length,
};
console.log(` ✅ Found ${catalog.length} catalog items`);
// Pick a random filter
const randomFilter = pickRandom(allFilters);
console.log(
` 🎲 Selected random filter: "${randomFilter.title}" (${randomFilter.filter})`
);
// Step 2: Test getPosts with random filter
console.log("\n📝 Step 2: Testing getPosts...");
const postsModule = this.loadModule(providerName, "posts");
if (!postsModule || !postsModule.getPosts) {
throw new Error("getPosts function not found");
}
const posts = await postsModule.getPosts({
filter: randomFilter.filter,
page: 1,
providerValue: providerName,
signal: this.signal,
providerContext,
});
if (!Array.isArray(posts) || posts.length === 0) {
throw new Error("getPosts returned empty or invalid result");
}
result.posts.success = true;
result.posts.data = { count: posts.length };
console.log(` ✅ Got ${posts.length} posts`);
// Pick random posts to test
const postsToTest = pickRandom(
posts,
Math.min(this.postsToTest, posts.length)
);
console.log(
` 🎲 Selected ${postsToTest.length} random posts for meta testing`
);
// Step 3: Test getMeta with random posts
console.log("\n📋 Step 3: Testing getMeta...");
const metaModule = this.loadModule(providerName, "meta");
if (!metaModule || !metaModule.getMeta) {
throw new Error("getMeta function not found");
}
const metaResults = [];
for (const post of postsToTest) {
console.log(`\n 📌 Testing: "${post.title.substring(0, 50)}..."`);
console.log(` Link: ${post.link}`);
try {
await sleep(500); // Small delay between requests
const meta = await metaModule.getMeta({
link: post.link,
providerContext,
});
if (!meta || !meta.linkList) {
console.log(
` ⚠️ Meta returned but linkList is empty/missing`
);
continue;
}
metaResults.push({ post, meta });
console.log(
` ✅ Got meta: type=${meta.type}, links=${meta.linkList.length}`
);
// Show link structure
meta.linkList.forEach((link, i) => {
const hasEpisodes = !!link.episodesLink;
const hasDirectLinks =
link.directLinks && link.directLinks.length > 0;
console.log(
` [${i + 1}] ${link.title.substring(0, 30)} - ${
hasEpisodes ? "📺 Episodes" : ""
}${hasDirectLinks ? "🎬 Direct" : ""}`
);
});
} catch (err) {
console.log(` ❌ Error: ${err.message}`);
}
}
if (metaResults.length === 0) {
throw new Error("No valid meta data retrieved");
}
result.meta.success = true;
result.meta.data = { testedCount: metaResults.length };
// Step 4: Test episodes OR stream based on meta content
console.log("\n🔗 Step 4: Testing Episodes/Stream...");
// Find links with episodes
const episodeLinks = [];
const directLinks = [];
for (const { meta } of metaResults) {
for (const link of meta.linkList) {
if (link.episodesLink) {
episodeLinks.push({ meta, link });
}
if (link.directLinks && link.directLinks.length > 0) {
directLinks.push({ meta, link });
}
}
}
// Test episodes if available
if (episodeLinks.length > 0) {
console.log(`\n 📺 Found ${episodeLinks.length} episode links`);
const episodesModule = this.loadModule(providerName, "episodes");
if (episodesModule && episodesModule.getEpisodes) {
const testEpisodeLink = pickRandom(episodeLinks);
console.log(
` 🎲 Testing episodes from: ${testEpisodeLink.link.title}`
);
console.log(` URL: ${testEpisodeLink.link.episodesLink}`);
try {
const episodes = await episodesModule.getEpisodes({
url: testEpisodeLink.link.episodesLink,
providerContext,
});
if (Array.isArray(episodes) && episodes.length > 0) {
result.episodes.success = true;
result.episodes.data = { count: episodes.length };
console.log(` ✅ Got ${episodes.length} episodes`);
// Show first few episodes
episodes.slice(0, 3).forEach((ep, i) => {
console.log(` [${i + 1}] ${ep.title}`);
});
// Test stream with random episode
console.log(`\n 🎬 Testing stream with random episode...`);
const randomEpisode = pickRandom(episodes);
console.log(` Episode: ${randomEpisode.title}`);
try {
const streamModule = this.loadModule(providerName, "stream");
if (streamModule && streamModule.getStream) {
const streams = await streamModule.getStream({
link: randomEpisode.link,
type: "series",
signal: this.signal,
providerContext,
});
if (Array.isArray(streams) && streams.length > 0) {
result.stream.success = true;
result.stream.data = {
count: streams.length,
type: "series",
};
console.log(` ✅ Got ${streams.length} stream(s)`);
streams.forEach((s, i) => {
console.log(
` [${i + 1}] ${s.server} - ${
s.quality || "unknown"
} quality`
);
});
} else {
console.log(` ⚠️ No streams returned`);
result.stream.error = "No streams returned";
}
}
} catch (err) {
console.log(` ❌ Stream error: ${err.message}`);
result.stream.error = err.message;
}
} else {
console.log(` ⚠️ No episodes returned`);
result.episodes.error = "No episodes returned";
}
} catch (err) {
console.log(` ❌ Episodes error: ${err.message}`);
result.episodes.error = err.message;
}
} else {
console.log(` ⚠️ getEpisodes function not found`);
result.episodes.skipped = true;
result.episodes.error = "Function not available";
}
} else {
result.episodes.skipped = true;
console.log(` No episode links found, skipping episodes test`);
}
// Test direct links/stream if episodes not tested or no episode links
if (directLinks.length > 0 && !result.stream.success) {
console.log(`\n 🎬 Found ${directLinks.length} direct link entries`);
const testDirectLink = pickRandom(directLinks);
const linksToTest = pickRandom(
testDirectLink.link.directLinks,
Math.min(this.linksToTest, testDirectLink.link.directLinks.length)
);
results[module] = {
success: true,
size: response.data.length,
hasExports: response.data.includes("exports."),
};
console.log(`${module}.js (${results[module].size} bytes)`);
} catch (error) {
results[module] = {
success: false,
error: error.response?.status === 404 ? "Not found" : error.message,
};
const isOptional = module === "episodes";
const icon = isOptional ? "⚠️ " : "❌";
console.log(
` ${icon} ${module}.js - ${results[module].error}${
isOptional ? " (optional)" : ""
}`
` 🎲 Testing ${linksToTest.length} random direct link(s)`
);
const streamModule = this.loadModule(providerName, "stream");
if (streamModule && streamModule.getStream) {
for (const directLink of Array.isArray(linksToTest)
? linksToTest
: [linksToTest]) {
console.log(`\n Testing: ${directLink.title}`);
console.log(` Link: ${directLink.link}`);
try {
await sleep(500);
const streams = await streamModule.getStream({
link: directLink.link,
type: directLink.type || "movie",
signal: this.signal,
providerContext,
});
if (Array.isArray(streams) && streams.length > 0) {
result.stream.success = true;
result.stream.data = {
count: streams.length,
type: directLink.type || "movie",
};
console.log(` ✅ Got ${streams.length} stream(s)`);
streams.forEach((s, i) => {
console.log(
` [${i + 1}] ${s.server} - ${
s.quality || "unknown"
} quality`
);
});
break; // One success is enough
} else {
console.log(` ⚠️ No streams returned`);
}
} catch (err) {
console.log(` ❌ Stream error: ${err.message}`);
result.stream.error = err.message;
}
}
} else {
console.log(` ❌ getStream function not found`);
result.stream.error = "Function not available";
}
} else if (
!result.stream.success &&
directLinks.length === 0 &&
episodeLinks.length === 0
) {
result.stream.skipped = true;
console.log(` No links to test stream with`);
}
} catch (error) {
console.log(`\n❌ Test failed: ${error.message}`);
// Determine which step failed
if (!result.catalog.success) {
result.catalog.error = error.message;
} else if (!result.posts.success) {
result.posts.error = error.message;
} else if (!result.meta.success) {
result.meta.error = error.message;
}
}
return results;
// Calculate summary
const steps = ["catalog", "posts", "meta", "episodes", "stream"];
for (const step of steps) {
if (result[step].success) {
result.summary.passed++;
} else if (result[step].skipped) {
result.summary.skipped++;
} else if (result[step].error) {
result.summary.failed++;
}
}
// Print summary
console.log(`\n${"─".repeat(60)}`);
console.log(`📊 Provider Summary: ${providerName}`);
console.log("─".repeat(60));
console.log(` ✅ Passed: ${result.summary.passed}`);
console.log(` ❌ Failed: ${result.summary.failed}`);
console.log(` ⏭️ Skipped: ${result.summary.skipped}`);
// List which steps passed/failed/skipped
console.log("\n Step Results:");
for (const step of steps) {
if (result[step].success) {
console.log(`${step}`);
} else if (result[step].skipped) {
console.log(` ⏭️ ${step} (skipped)`);
} else if (result[step].error) {
console.log(`${step}: ${result[step].error}`);
} else {
console.log(`${step} (not tested)`);
}
}
const statusIcon = result.summary.failed === 0 ? "✅" : "❌";
console.log(
`\n ${statusIcon} Overall: ${
result.summary.failed === 0 ? "PASSED" : "FAILED"
}`
);
return result;
}
/**
* Test all providers
*/
async testAllProviders() {
console.log("🚀 Starting comprehensive provider test...\n");
console.log("🚀 Starting comprehensive provider tests...\n");
// Test connection
const connected = await this.testConnection();
if (!connected) return;
// Test manifest
const providers = await this.testManifest();
if (!providers) return;
// Test each provider
const results = {};
for (const provider of providers) {
results[provider.value] = await this.testProvider(provider.value);
}
// Summary
console.log("\n📊 Test Summary:");
console.log("=".repeat(50));
let totalProviders = 0;
let passedProviders = 0;
for (const [providerName, modules] of Object.entries(results)) {
totalProviders++;
const requiredModules = ["catalog", "posts", "meta", "stream"];
const passedRequired = requiredModules.every(
(mod) => modules[mod]?.success
);
if (passedRequired) {
passedProviders++;
console.log(`${providerName} - All required modules OK`);
} else {
console.log(`${providerName} - Missing required modules`);
}
}
console.log(
`\n📈 Results: ${passedProviders}/${totalProviders} providers passed`
);
if (passedProviders === totalProviders) {
console.log("🎉 All providers are ready for testing!");
} else {
console.log("⚠️ Some providers need attention before testing.");
}
return results;
}
/**
* Trigger rebuild on server
*/
async rebuild() {
try {
console.log("🔨 Triggering rebuild...");
const response = await this.axios.post("/build");
console.log("✅ Rebuild completed");
return true;
} catch (error) {
console.error(
"❌ Rebuild failed:",
error.response?.data?.error || error.message
);
return false;
}
}
/**
* Get server status
*/
async getStatus() {
try {
const response = await this.axios.get("/status");
const status = response.data;
console.log("📊 Server Status:");
console.log(` 🟢 Status: ${status.status}`);
console.log(` 🔌 Port: ${status.port}`);
console.log(` 📦 Providers: ${status.providers}`);
console.log(` 🕐 Last Build: ${status.buildTime || "Never"}`);
if (status.providerList.length > 0) {
console.log(" 📋 Available Providers:");
status.providerList.forEach((p) => console.log(`${p}`));
}
return status;
} catch (error) {
console.error("❌ Failed to get status:", error.message);
if (!providerContext) {
console.log("❌ Provider context not loaded. Run 'npm run build' first.");
return null;
}
const providers = this.getAvailableProviders();
if (providers.length === 0) {
console.log("❌ No providers found.");
return null;
}
console.log(`📦 Found ${providers.length} providers to test:`);
providers.forEach((p) => console.log(`${p}`));
const results = {};
let passed = 0;
let failed = 0;
for (const provider of providers) {
try {
results[provider] = await this.testProvider(provider);
if (results[provider].summary.failed === 0) {
passed++;
} else {
failed++;
}
} catch (error) {
console.log(
`\n❌ Critical error testing ${provider}: ${error.message}`
);
failed++;
results[provider] = { error: error.message };
}
// Small delay between providers
await sleep(1000);
}
// Final summary
console.log(`\n${"═".repeat(60)}`);
console.log("📊 FINAL TEST SUMMARY");
console.log("═".repeat(60));
console.log(` Total Providers: ${providers.length}`);
console.log(` ✅ Passed: ${passed}`);
console.log(` ❌ Failed: ${failed}`);
// List failed providers with details
if (failed > 0) {
console.log(`\n${"─".repeat(60)}`);
console.log("❌ FAILED PROVIDERS:");
console.log("─".repeat(60));
for (const [name, result] of Object.entries(results)) {
if (result.error) {
// Critical error
console.log(`\n${name}`);
console.log(` Error: ${result.error}`);
} else if (result.summary?.failed > 0) {
// Step failures
console.log(`\n${name}`);
const steps = ["catalog", "posts", "meta", "episodes", "stream"];
for (const step of steps) {
if (result[step]?.error && !result[step]?.skipped) {
console.log(`${step}: ${result[step].error}`);
}
}
}
}
}
// List passed providers
if (passed > 0) {
console.log(`\n${"─".repeat(60)}`);
console.log("✅ PASSED PROVIDERS:");
console.log("─".repeat(60));
const passedProviders = Object.entries(results)
.filter(([_, result]) => result.summary?.failed === 0 && !result.error)
.map(([name]) => name);
console.log(` ${passedProviders.join(", ")}`);
}
console.log(`\n${"═".repeat(60)}`);
return results;
}
}
@@ -191,53 +593,52 @@ class ProviderTester {
*/
async function main() {
const args = process.argv.slice(2);
const command = args[0] || "test";
const providerName = args[1];
const providerName = args[0];
const tester = new ProviderTester();
// Check for options
const postsToTest =
parseInt(args.find((a) => a.startsWith("--posts="))?.split("=")[1]) || 2;
const linksToTest =
parseInt(args.find((a) => a.startsWith("--links="))?.split("=")[1]) || 2;
switch (command) {
case "test":
if (providerName) {
await tester.testProvider(providerName);
} else {
await tester.testAllProviders();
}
break;
const tester = new ProviderTester({ postsToTest, linksToTest });
case "status":
await tester.getStatus();
break;
if (args.includes("--help") || args.includes("-h")) {
console.log(`
🎯 Vega Providers Integration Tester
=====================================
case "rebuild":
await tester.rebuild();
break;
Usage: node test-providers.js [provider] [options]
case "connection":
await tester.testConnection();
break;
Arguments:
provider Name of specific provider to test (optional)
If not provided, tests all providers
case "manifest":
await tester.testManifest();
break;
Options:
--posts=N Number of random posts to test (default: 2)
--links=N Number of random direct links to test (default: 2)
--help, -h Show this help message
default:
console.log(`
Usage: node test-providers.js [command] [provider]
Commands:
test [provider] - Test all providers or specific provider
status - Show server status
rebuild - Trigger rebuild
connection - Test server connection
manifest - Test manifest endpoint
Test Flow:
1. Load catalog → pick random filter
2. Call getPosts with filter
3. Pick random posts → call getMeta
4. If episodesLink → call getEpisodes → getStream
5. If directLinks → call getStream
Examples:
node test-providers.js # Test all providers
node test-providers.js test vega # Test specific provider
node test-providers.js status # Show status
node test-providers.js rebuild # Rebuild and test
`);
node test-providers.js # Test all providers
node test-providers.js vega # Test only vega provider
node test-providers.js mod --posts=3 # Test mod with 3 random posts
node test-providers.js --posts=1 --links=1 # Quick test all providers
`);
return;
}
if (providerName && !providerName.startsWith("--")) {
await tester.testProvider(providerName);
} else {
await tester.testAllProviders();
}
}