clean up
This commit is contained in:
10
eslint.config.js
Normal file
10
eslint.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import js from '@eslint/js';
|
||||||
|
import globals from 'globals';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
import {defineConfig} from 'eslint/config';
|
||||||
|
|
||||||
|
export default defineConfig([
|
||||||
|
{files: ['**/*.{js,mjs,cjs,ts}'], plugins: {js}, extends: ['js/recommended']},
|
||||||
|
{files: ['**/*.{js,mjs,cjs,ts}'], languageOptions: {globals: globals.node}},
|
||||||
|
tseslint.configs.recommended
|
||||||
|
]);
|
||||||
13
package.json
13
package.json
@@ -3,16 +3,21 @@
|
|||||||
"version": "2025.0.0",
|
"version": "2025.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint . --fix"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@eslint/js": "9.33.0",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"globals": "16.3.0",
|
||||||
|
"typescript-eslint": "8.40.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "5.9.8"
|
"typescript": "5.9.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@techniker-me/pcast-api": "2025.0.2",
|
|
||||||
"@techniker-me/rtmp-push": "2025.0.2",
|
|
||||||
"commander": "14.0.0",
|
"commander": "14.0.0",
|
||||||
"eslint": "8"
|
"eslint": "9.33.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import SupportedBrowser from "./SupportedBrowser";
|
import SupportedBrowser from './SupportedBrowser';
|
||||||
|
|
||||||
// Source: https://github.com/browserstack/api
|
// Source: https://github.com/browserstack/api
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@ export class BrowserstackApi {
|
|||||||
public async getSupportedBrowsers(): Promise<SupportedBrowser[]> {
|
public async getSupportedBrowsers(): Promise<SupportedBrowser[]> {
|
||||||
const endpoint = `${this._baseUrl}/browsers?flat=true`;
|
const endpoint = `${this._baseUrl}/browsers?flat=true`;
|
||||||
const headers = {
|
const headers = {
|
||||||
'Authorization': this._authorizationHeader
|
Authorization: this._authorizationHeader
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(endpoint, {headers});
|
const response = await fetch(endpoint, {headers});
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ type SupportedBrowser = {
|
|||||||
device: string;
|
device: string;
|
||||||
browser_version: string | null;
|
browser_version: string | null;
|
||||||
real_mobile: boolean;
|
real_mobile: boolean;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default SupportedBrowser;
|
export default SupportedBrowser;
|
||||||
@@ -32,7 +32,15 @@ export default class CommandLine {
|
|||||||
public static parse(args: string[]): CommandLineOptions {
|
public static parse(args: string[]): CommandLineOptions {
|
||||||
CommandLine._program.parse(args);
|
CommandLine._program.parse(args);
|
||||||
|
|
||||||
return CommandLine._program.opts<CommandLineOptions>();
|
const rawOptions = CommandLine._program.opts();
|
||||||
|
|
||||||
|
// Convert the string log level back to LoggingLevel enum
|
||||||
|
const logLevel = rawOptions.logLevel ? LoggingLevelMapping.convertLoggingLevelTypeToLoggingLevel(rawOptions.logLevel) : defaultLogLevel;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...rawOptions,
|
||||||
|
logLevel
|
||||||
|
} as CommandLineOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
static {
|
static {
|
||||||
@@ -55,15 +63,25 @@ export default class CommandLine {
|
|||||||
|
|
||||||
CommandLine._program.option('--ingest-uri <ingestUri>', 'The ingest URI to use');
|
CommandLine._program.option('--ingest-uri <ingestUri>', 'The ingest URI to use');
|
||||||
CommandLine._program.option('--publisher-uri <publisherUri>', 'The publisher URI to use');
|
CommandLine._program.option('--publisher-uri <publisherUri>', 'The publisher URI to use');
|
||||||
CommandLine._program.option('--viewer <browser@version:OS@OSVersion...>', 'The browser and OS for simulating a viewer to use', handleArrayOption, defaultViewers);
|
CommandLine._program.option(
|
||||||
CommandLine._program.option('--publisher <browser@version:OS@OSVersion...>', 'The browser and OS for simulating a publisher to use', handleArrayOption, defaultPublishers);
|
'--viewer <browser@version:OS@OSVersion...>',
|
||||||
|
'The browser and OS for simulating a viewer to use',
|
||||||
|
handleArrayOption,
|
||||||
|
defaultViewers
|
||||||
|
);
|
||||||
|
CommandLine._program.option(
|
||||||
|
'--publisher <browser@version:OS@OSVersion...>',
|
||||||
|
'The browser and OS for simulating a publisher to use',
|
||||||
|
handleArrayOption,
|
||||||
|
defaultPublishers
|
||||||
|
);
|
||||||
CommandLine._program.option('-t, --test <test...>', 'The test to run', handleArrayOption, defaultTests);
|
CommandLine._program.option('-t, --test <test...>', 'The test to run', handleArrayOption, defaultTests);
|
||||||
CommandLine._program.option('--use-browserstack', 'Run tests using BrowserStack', defaultUseBrowserstack);
|
CommandLine._program.option('--use-browserstack', 'Run tests using BrowserStack', defaultUseBrowserstack);
|
||||||
CommandLine._program.option('--use-browserstack-local', 'Run tests using BrowserStack Local', defaultUseBrowserstackLocal);
|
CommandLine._program.option('--use-browserstack-local', 'Run tests using BrowserStack Local', defaultUseBrowserstackLocal);
|
||||||
CommandLine._program.option('--browserstack-user <username>', 'The BrowserStack username to use', process.env.BROWSERSTACK_USER || '');
|
CommandLine._program.option('--browserstack-user <username>', 'The BrowserStack username to use', process.env.BROWSERSTACK_USER || '');
|
||||||
CommandLine._program.option('--browserstack-key <key>', 'The BrowserStack key to use', process.env.BROWSERSTACK_KEY || '');
|
CommandLine._program.option('--browserstack-key <key>', 'The BrowserStack key to use', process.env.BROWSERSTACK_KEY || '');
|
||||||
CommandLine._program.option('--log-level <logLevel>', 'The log level to use', LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(defaultLogLevel));
|
CommandLine._program.option('--log-level <logLevel>', 'The log level to use', LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(defaultLogLevel));
|
||||||
}
|
};
|
||||||
|
|
||||||
setupProgramOptions();
|
setupProgramOptions();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import CommandLine, { CommandLineOptions } from './CommandLine';
|
import type {CommandLineOptions} from './CommandLine';
|
||||||
|
|
||||||
|
|
||||||
type ApplicationCredentials = {
|
type ApplicationCredentials = {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
secret: string;
|
secret: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type Uri = {
|
type Uri = {
|
||||||
pcast: string;
|
pcast: string;
|
||||||
ingest: string | undefined;
|
ingest: string | undefined;
|
||||||
channel: string | undefined;
|
channel: string | undefined;
|
||||||
publisher: string | undefined;
|
publisher: string | undefined;
|
||||||
}
|
};
|
||||||
|
|
||||||
type BrowserstackConfiguration = {
|
type BrowserstackConfiguration = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
local: boolean;
|
local: boolean;
|
||||||
user: string;
|
user: string;
|
||||||
key: string;
|
key: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default class TestConfiguration {
|
export default class TestConfiguration {
|
||||||
private readonly _applicationCredentials: ApplicationCredentials;
|
private readonly _applicationCredentials: ApplicationCredentials;
|
||||||
@@ -31,14 +30,14 @@ export default class TestConfiguration {
|
|||||||
constructor(commandLineOptions: CommandLineOptions) {
|
constructor(commandLineOptions: CommandLineOptions) {
|
||||||
this._applicationCredentials = {
|
this._applicationCredentials = {
|
||||||
applicationId: commandLineOptions.applicationId,
|
applicationId: commandLineOptions.applicationId,
|
||||||
secret: commandLineOptions.secret,
|
secret: commandLineOptions.secret
|
||||||
};
|
};
|
||||||
|
|
||||||
this._uri = {
|
this._uri = {
|
||||||
pcast: commandLineOptions.pcastUri,
|
pcast: commandLineOptions.pcastUri,
|
||||||
ingest: commandLineOptions.ingestUri,
|
ingest: commandLineOptions.ingestUri,
|
||||||
channel: commandLineOptions.channelUri,
|
channel: commandLineOptions.channelUri,
|
||||||
publisher: commandLineOptions.publisherUri,
|
publisher: commandLineOptions.publisherUri
|
||||||
};
|
};
|
||||||
|
|
||||||
this._viewers = commandLineOptions.viewers;
|
this._viewers = commandLineOptions.viewers;
|
||||||
@@ -48,7 +47,7 @@ export default class TestConfiguration {
|
|||||||
enabled: commandLineOptions.useBrowserstack,
|
enabled: commandLineOptions.useBrowserstack,
|
||||||
local: commandLineOptions.useBrowserstackLocal,
|
local: commandLineOptions.useBrowserstackLocal,
|
||||||
user: commandLineOptions.browserstackUser,
|
user: commandLineOptions.browserstackUser,
|
||||||
key: commandLineOptions.browserstackKey,
|
key: commandLineOptions.browserstackKey
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,7 +96,8 @@ export class Logger {
|
|||||||
|
|
||||||
// Append remaining parameters if any
|
// Append remaining parameters if any
|
||||||
if (paramIndex < optionalParameters.length) {
|
if (paramIndex < optionalParameters.length) {
|
||||||
const remainingParams = optionalParameters.slice(paramIndex)
|
const remainingParams = optionalParameters
|
||||||
|
.slice(paramIndex)
|
||||||
.map(param => this.parameterToString(param))
|
.map(param => this.parameterToString(param))
|
||||||
.join(' ');
|
.join(' ');
|
||||||
result += ` ${remainingParams}`;
|
result += ` ${remainingParams}`;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {ConsoleAppender} from './appenders/ConsoleAppender';
|
import {ConsoleAppender} from './appenders/ConsoleAppender';
|
||||||
import IAppender from './appenders/IAppender';
|
import IAppender from './appenders/IAppender';
|
||||||
import {Logger} from './Logger';
|
import {Logger} from './Logger';
|
||||||
import {LoggingLevel, LoggingLevelMapping, LoggingLevelType} from './LoggingLevel';
|
import {LoggingLevel, LoggingLevelMapping} from './LoggingLevel';
|
||||||
import {Threshold} from './Threshold';
|
import {Threshold} from './Threshold';
|
||||||
|
|
||||||
export default class LoggerFactory {
|
export default class LoggerFactory {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import {browser} from '@wdio/globals';
|
import {browser} from '@wdio/globals';
|
||||||
|
|
||||||
|
|
||||||
export type PageOptions = {
|
export type PageOptions = {
|
||||||
browser?: typeof browser; // MultiRemote usecase
|
browser?: typeof browser; // MultiRemote usecase
|
||||||
|
};
|
||||||
}
|
|
||||||
|
|
||||||
export type PageOpenOptions = {
|
export type PageOpenOptions = {
|
||||||
queryParameters?: Record<string, string | number>;
|
queryParameters?: Record<string, string | number>;
|
||||||
@@ -22,7 +20,9 @@
|
|||||||
|
|
||||||
public async open(options: PageOpenOptions = {}): Promise<void> {
|
public async open(options: PageOpenOptions = {}): Promise<void> {
|
||||||
const {queryParameters, isNewTabRequest, endpoint, requestPath} = options;
|
const {queryParameters, isNewTabRequest, endpoint, requestPath} = options;
|
||||||
const pageUrl = `${this._baseUrl}/${endpoint}${requestPath}?${Object.entries(queryParameters ?? {}).map(([queryParameterName, queryParamterValue]) => (`${queryParameterName}=${queryParamterValue}&`)).join('')}`;
|
const pageUrl = `${this._baseUrl}/${endpoint}${requestPath}?${Object.entries(queryParameters ?? {})
|
||||||
|
.map(([queryParameterName, queryParamterValue]) => `${queryParameterName}=${queryParamterValue}&`)
|
||||||
|
.join('')}`;
|
||||||
|
|
||||||
if (isNewTabRequest) {
|
if (isNewTabRequest) {
|
||||||
await browser.newWindow(pageUrl);
|
await browser.newWindow(pageUrl);
|
||||||
|
|||||||
@@ -14,5 +14,4 @@ export class SubscribingPage extends Page {
|
|||||||
public async open(options?: PageOpenOptions): Promise<void> {
|
public async open(options?: PageOpenOptions): Promise<void> {
|
||||||
await super.open(options);
|
await super.open(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,203 +0,0 @@
|
|||||||
import {before, describe, it, after} from 'mocha';
|
|
||||||
import {expect, use} from 'chai';
|
|
||||||
import ChaiAsPromised from 'chai-as-promised';
|
|
||||||
import {SubscribingPage} from '../pages';
|
|
||||||
|
|
||||||
use(ChaiAsPromised);
|
|
||||||
|
|
||||||
// Helper function to add delays and make tests more observable
|
|
||||||
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
|
||||||
|
|
||||||
// Simple, working test implementation
|
|
||||||
describe('Subscribe Workflow Tests', () => {
|
|
||||||
let subscribingPage: SubscribingPage;
|
|
||||||
|
|
||||||
before(async () => {
|
|
||||||
console.log('\n🚀 Starting Subscribe Workflow Tests...');
|
|
||||||
console.log('⏱️ Tests will run with delays for real-time observation');
|
|
||||||
await delay(1000);
|
|
||||||
|
|
||||||
// Initialize the page object with a real URL
|
|
||||||
const testUrl = 'http://dl.phenixrts.com/JsSDK/2025.2.latest/examples/channel-viewer-plain.html';
|
|
||||||
subscribingPage = new SubscribingPage(testUrl, {});
|
|
||||||
console.log(`✅ SubscribingPage initialized with URL: ${testUrl}`);
|
|
||||||
await delay(500);
|
|
||||||
});
|
|
||||||
|
|
||||||
after(async () => {
|
|
||||||
console.log('\n🧹 Test cleanup completed');
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('RealTime Stream Subscription', () => {
|
|
||||||
it('should initialize page object correctly', async () => {
|
|
||||||
console.log('🔍 Testing page initialization...');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
expect(subscribingPage).to.exist;
|
|
||||||
console.log('✅ Page object exists');
|
|
||||||
await delay(200);
|
|
||||||
|
|
||||||
expect(subscribingPage).to.be.instanceOf(SubscribingPage);
|
|
||||||
console.log('✅ Page object is correct instance');
|
|
||||||
await delay(200);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should open the streaming page successfully', async () => {
|
|
||||||
console.log('🌐 Testing page navigation...');
|
|
||||||
await delay(500);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Open the page with test parameters
|
|
||||||
await subscribingPage.open({
|
|
||||||
queryParameters: {
|
|
||||||
channelId: 'test-channel-realtime',
|
|
||||||
token: 'test-token-realtime',
|
|
||||||
streamType: 'realtime'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
console.log('✅ Page opened successfully');
|
|
||||||
await delay(1000);
|
|
||||||
|
|
||||||
// Wait for page to load
|
|
||||||
console.log('⏳ Waiting for page to load...');
|
|
||||||
await delay(2000);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('⚠️ Page navigation failed:', error);
|
|
||||||
throw error; // Fail the test if navigation fails
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should find video element on the page', async () => {
|
|
||||||
console.log('🔍 Looking for video element...');
|
|
||||||
await delay(500);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const videoElement = await subscribingPage.videoElement;
|
|
||||||
console.log('📺 Video element found:', videoElement ? 'Yes' : 'No');
|
|
||||||
|
|
||||||
if (videoElement) {
|
|
||||||
expect(videoElement).to.exist;
|
|
||||||
console.log('✅ Video element exists and is accessible');
|
|
||||||
} else {
|
|
||||||
console.log('⚠️ No video element found - this might indicate a page loading issue');
|
|
||||||
// Don't fail the test, but log the issue
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('⚠️ Video element test failed:', error);
|
|
||||||
// Don't fail the test for video element issues
|
|
||||||
}
|
|
||||||
|
|
||||||
await delay(500);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('HLS Stream Subscription', () => {
|
|
||||||
it('should handle HLS stream configuration', async () => {
|
|
||||||
console.log('🔍 Testing HLS stream configuration...');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
const streamConfig = {
|
|
||||||
type: 'hls',
|
|
||||||
url: 'https://example.com/stream.m3u8',
|
|
||||||
quality: 'high'
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(streamConfig.type).to.equal('hls');
|
|
||||||
console.log('✅ HLS stream type configured');
|
|
||||||
await delay(200);
|
|
||||||
|
|
||||||
expect(streamConfig.url).to.include('.m3u8');
|
|
||||||
console.log('✅ HLS playlist URL format correct');
|
|
||||||
await delay(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DASH Stream Subscription', () => {
|
|
||||||
it('should handle DASH stream configuration', async () => {
|
|
||||||
console.log('🔍 Testing DASH stream configuration...');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
const streamConfig = {
|
|
||||||
type: 'dash',
|
|
||||||
url: 'https://example.com/stream.mpd',
|
|
||||||
quality: 'adaptive'
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(streamConfig.type).to.equal('dash');
|
|
||||||
console.log('✅ DASH stream type configured');
|
|
||||||
await delay(200);
|
|
||||||
|
|
||||||
expect(streamConfig.url).to.include('.mpd');
|
|
||||||
console.log('✅ DASH manifest URL format correct');
|
|
||||||
await delay(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('WebRTC Stream Subscription', () => {
|
|
||||||
it('should handle WebRTC stream configuration', async () => {
|
|
||||||
console.log('🔍 Testing WebRTC stream configuration...');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
const streamConfig = {
|
|
||||||
type: 'webrtc',
|
|
||||||
url: 'wss://example.com/webrtc',
|
|
||||||
latency: 'low'
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(streamConfig.type).to.equal('webrtc');
|
|
||||||
console.log('✅ WebRTC stream type configured');
|
|
||||||
await delay(200);
|
|
||||||
|
|
||||||
expect(streamConfig.url).to.include('wss://');
|
|
||||||
console.log('✅ WebRTC WebSocket URL format correct');
|
|
||||||
await delay(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('Stream Subscription Workflow', () => {
|
|
||||||
it('should demonstrate complete subscription workflow', async () => {
|
|
||||||
console.log('🔍 Testing complete subscription workflow...');
|
|
||||||
await delay(500);
|
|
||||||
|
|
||||||
// Step 1: Channel setup
|
|
||||||
const channel = {
|
|
||||||
id: 'workflow-test-channel',
|
|
||||||
name: 'Workflow Test Channel',
|
|
||||||
status: 'active'
|
|
||||||
};
|
|
||||||
expect(channel.status).to.equal('active');
|
|
||||||
console.log('✅ Channel setup completed');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
// Step 2: Authentication
|
|
||||||
const auth = {
|
|
||||||
token: 'workflow-test-token',
|
|
||||||
expires: Date.now() + 3600000, // 1 hour from now
|
|
||||||
permissions: ['view', 'subscribe']
|
|
||||||
};
|
|
||||||
expect(auth.permissions).to.include('subscribe');
|
|
||||||
console.log('✅ Authentication configured');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
// Step 3: Stream connection
|
|
||||||
const connection = {
|
|
||||||
status: 'connecting',
|
|
||||||
streamType: 'realtime',
|
|
||||||
quality: 'high'
|
|
||||||
};
|
|
||||||
expect(connection.status).to.equal('connecting');
|
|
||||||
console.log('✅ Stream connection initiated');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
// Step 4: Subscription active
|
|
||||||
connection.status = 'active';
|
|
||||||
expect(connection.status).to.equal('active');
|
|
||||||
console.log('✅ Subscription active');
|
|
||||||
await delay(300);
|
|
||||||
|
|
||||||
console.log('🎯 Complete workflow test passed');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import { ITestContext, IChannelTestContext, IPublisherTestContext, ISubscriberTestContext } from '../interfaces/ITestContext';
|
|
||||||
|
|
||||||
export interface TestContextConfig {
|
|
||||||
applicationCredentials: {
|
|
||||||
id: string;
|
|
||||||
secret: string;
|
|
||||||
};
|
|
||||||
uri: {
|
|
||||||
pcast: string;
|
|
||||||
ingest: string;
|
|
||||||
channel: string;
|
|
||||||
};
|
|
||||||
streamKind: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class TestContextFactory {
|
|
||||||
private static createBaseContext(config: TestContextConfig): ITestContext {
|
|
||||||
return {
|
|
||||||
applicationCredentials: config.applicationCredentials,
|
|
||||||
uri: config.uri,
|
|
||||||
streamKind: config.streamKind,
|
|
||||||
cleanup: [],
|
|
||||||
addCleanupTask: function(task: () => Promise<void>): void {
|
|
||||||
this.cleanup.push(task);
|
|
||||||
},
|
|
||||||
executeCleanup: async function(): Promise<void> {
|
|
||||||
for (const task of this.cleanup) {
|
|
||||||
try {
|
|
||||||
await task();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Cleanup task failed:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static createChannelContext(config: TestContextConfig): IChannelTestContext {
|
|
||||||
const baseContext = this.createBaseContext(config);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to import the real PCastApi if available
|
|
||||||
const PCastApi = require('@techniker-me/pcast-api');
|
|
||||||
const pcastApi = new PCastApi(config.uri.pcast, config.applicationCredentials);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...baseContext,
|
|
||||||
pcastApi,
|
|
||||||
channel: null as any, // Will be set later
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Real PCastApi not available, using mock API:', error);
|
|
||||||
|
|
||||||
// Return mock context for testing
|
|
||||||
return {
|
|
||||||
...baseContext,
|
|
||||||
pcastApi: {
|
|
||||||
createChannel: async () => ({ channelId: 'mock-channel' }),
|
|
||||||
deleteChannel: async () => ({ status: 'ok' })
|
|
||||||
} as any,
|
|
||||||
channel: null as any,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createPublisherContext(config: TestContextConfig): IPublisherTestContext {
|
|
||||||
const channelContext = this.createChannelContext(config);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try to import the real RtmpPush if available
|
|
||||||
const RtmpPush = require('@techniker-me/rtmp-push');
|
|
||||||
|
|
||||||
return {
|
|
||||||
...channelContext,
|
|
||||||
publishSource: new RtmpPush(),
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Real RtmpPush not available, using mock:', error);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...channelContext,
|
|
||||||
publishSource: {} as any,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static createSubscriberContext(config: TestContextConfig): ISubscriberTestContext {
|
|
||||||
const channelContext = this.createChannelContext(config);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...channelContext,
|
|
||||||
publishDestination: {
|
|
||||||
token: '',
|
|
||||||
capabilities: [],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
export interface ITestContext {
|
|
||||||
readonly applicationCredentials: {
|
|
||||||
readonly id: string;
|
|
||||||
readonly secret: string;
|
|
||||||
};
|
|
||||||
readonly uri: {
|
|
||||||
readonly pcast: string;
|
|
||||||
readonly ingest: string;
|
|
||||||
readonly channel: string;
|
|
||||||
};
|
|
||||||
readonly streamKind: string;
|
|
||||||
readonly cleanup: (() => Promise<void>)[];
|
|
||||||
|
|
||||||
addCleanupTask(task: () => Promise<void>): void;
|
|
||||||
executeCleanup(): Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IChannelTestContext extends ITestContext {
|
|
||||||
channel: any; // Mutable for test setup
|
|
||||||
readonly pcastApi: any; // Replace 'any' with proper PCastApi type
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IPublisherTestContext extends IChannelTestContext {
|
|
||||||
readonly publishSource: any; // Replace 'any' with proper RtmpPush type
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISubscriberTestContext extends IChannelTestContext {
|
|
||||||
publishDestination: { // Mutable for test setup
|
|
||||||
token: string;
|
|
||||||
readonly capabilities: string[];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
import { IChannelTestContext } from '../interfaces/ITestContext';
|
|
||||||
|
|
||||||
// Mock Channel type for now to avoid import issues
|
|
||||||
interface MockChannel {
|
|
||||||
channelId: string;
|
|
||||||
status?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChannelService {
|
|
||||||
static async createTestChannel(context: IChannelTestContext): Promise<MockChannel> {
|
|
||||||
try {
|
|
||||||
// Try to import the real API if available
|
|
||||||
const { Channel } = await import('@techniker-me/pcast-api');
|
|
||||||
|
|
||||||
const channelAlias = `UAT#${new Date().toISOString()}#${context.streamKind}`;
|
|
||||||
const channelDescription = `UAT#SubscribeWorkflow#${context.streamKind}`;
|
|
||||||
const channelOptions: string[] = [];
|
|
||||||
|
|
||||||
const channel = await context.pcastApi.createChannel(channelAlias, channelDescription, channelOptions);
|
|
||||||
|
|
||||||
// Add cleanup task
|
|
||||||
context.addCleanupTask(async () => {
|
|
||||||
console.log(`${new Date().toISOString()} [SubscribeWorkflow] [cleanup] deleting channel [${JSON.stringify(channel, null, 2)}]`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const deleteResponse = await context.pcastApi.deleteChannel(channel.channelId);
|
|
||||||
|
|
||||||
if (deleteResponse.status !== 'ok') {
|
|
||||||
throw new Error(`[SubscribeWorkflow] [cleanup] Error: Unable to delete test channel due to [${JSON.stringify(deleteResponse, null, 2)}]`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to delete channel during cleanup:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return channel;
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Real PCast API not available, using mock channel:', error);
|
|
||||||
|
|
||||||
// Return mock channel for testing
|
|
||||||
const mockChannel: MockChannel = {
|
|
||||||
channelId: `mock-${Date.now()}-${context.streamKind}`
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add mock cleanup task
|
|
||||||
context.addCleanupTask(async () => {
|
|
||||||
console.log(`${new Date().toISOString()} [SubscribeWorkflow] [cleanup] cleaning up mock channel [${mockChannel.channelId}]`);
|
|
||||||
});
|
|
||||||
|
|
||||||
return mockChannel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import { ISubscriberTestContext } from '../interfaces/ITestContext';
|
|
||||||
|
|
||||||
export class TokenService {
|
|
||||||
static generateSubscriberToken(context: ISubscriberTestContext): string {
|
|
||||||
try {
|
|
||||||
// Try to import the real TokenBuilder if available
|
|
||||||
const TokenBuilder = require('phenix-edge-auth');
|
|
||||||
const { browser } = require('@wdio/globals');
|
|
||||||
|
|
||||||
const { capabilities: { browserName, browserVersion, platformName } } = browser;
|
|
||||||
|
|
||||||
const subscriberTag = `UAT#${new Date().toISOString()}#SubscribeWorkflow#${context.streamKind}#${browserName}@${browserVersion}:${platformName}`;
|
|
||||||
|
|
||||||
const tokenBuilder = new TokenBuilder()
|
|
||||||
.withApplicationId(context.applicationCredentials.id)
|
|
||||||
.withSecret(context.applicationCredentials.secret)
|
|
||||||
.expiresInSeconds(3600)
|
|
||||||
.forChannel(context.channel.channelId)
|
|
||||||
.applyTags(subscriberTag);
|
|
||||||
|
|
||||||
// Add capabilities if they exist
|
|
||||||
context.publishDestination.capabilities?.forEach(subscriberCapability =>
|
|
||||||
tokenBuilder.withCapability(subscriberCapability)
|
|
||||||
);
|
|
||||||
|
|
||||||
return tokenBuilder.build();
|
|
||||||
} catch (error) {
|
|
||||||
console.warn('Real TokenBuilder not available, using mock token:', error);
|
|
||||||
|
|
||||||
// Return mock token for testing
|
|
||||||
return `mock-token-${Date.now()}-${context.streamKind}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
export interface IStreamKindStrategy {
|
|
||||||
getStreamKind(): string;
|
|
||||||
getChannelOptions(): string[];
|
|
||||||
getTestDescription(): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class RealTimeStreamStrategy implements IStreamKindStrategy {
|
|
||||||
getStreamKind(): string {
|
|
||||||
return 'RealTime';
|
|
||||||
}
|
|
||||||
|
|
||||||
getChannelOptions(): string[] {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
getTestDescription(): string {
|
|
||||||
return 'RealTime stream subscription test';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChunkedStreamHLSStrategy implements IStreamKindStrategy {
|
|
||||||
getStreamKind(): string {
|
|
||||||
return 'ChunkedStream HLS';
|
|
||||||
}
|
|
||||||
|
|
||||||
getChannelOptions(): string[] {
|
|
||||||
return ['hls'];
|
|
||||||
}
|
|
||||||
|
|
||||||
getTestDescription(): string {
|
|
||||||
return 'ChunkedStream HLS subscription test';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ChunkedStreamDASHStrategy implements IStreamKindStrategy {
|
|
||||||
getStreamKind(): string {
|
|
||||||
return 'ChunkedStream DASH';
|
|
||||||
}
|
|
||||||
|
|
||||||
getChannelOptions(): string[] {
|
|
||||||
return ['dash'];
|
|
||||||
}
|
|
||||||
|
|
||||||
getTestDescription(): string {
|
|
||||||
return 'ChunkedStream DASH subscription test';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Example of how easy it is to add new stream types
|
|
||||||
export class WebRTCStreamStrategy implements IStreamKindStrategy {
|
|
||||||
getStreamKind(): string {
|
|
||||||
return 'WebRTC';
|
|
||||||
}
|
|
||||||
|
|
||||||
getChannelOptions(): string[] {
|
|
||||||
return ['webrtc', 'low-latency'];
|
|
||||||
}
|
|
||||||
|
|
||||||
getTestDescription(): string {
|
|
||||||
return 'WebRTC stream subscription test';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class StreamKindStrategyFactory {
|
|
||||||
static createStrategy(streamType: string): IStreamKindStrategy {
|
|
||||||
switch (streamType.toLowerCase()) {
|
|
||||||
case 'realtime':
|
|
||||||
return new RealTimeStreamStrategy();
|
|
||||||
case 'hls':
|
|
||||||
return new ChunkedStreamHLSStrategy();
|
|
||||||
case 'dash':
|
|
||||||
return new ChunkedStreamDASHStrategy();
|
|
||||||
case 'webrtc':
|
|
||||||
return new WebRTCStreamStrategy();
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown stream type: ${streamType}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user