diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..e56a2bf --- /dev/null +++ b/eslint.config.js @@ -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 +]); diff --git a/package.json b/package.json index 79f64fd..ff89ef6 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,21 @@ "version": "2025.0.0", "type": "module", "private": true, + "scripts": { + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, "devDependencies": { - "@types/bun": "latest" + "@eslint/js": "9.33.0", + "@types/bun": "latest", + "globals": "16.3.0", + "typescript-eslint": "8.40.0" }, "peerDependencies": { "typescript": "5.9.8" }, "dependencies": { - "@techniker-me/pcast-api": "2025.0.2", - "@techniker-me/rtmp-push": "2025.0.2", "commander": "14.0.0", - "eslint": "8" + "eslint": "9.33.0" } } diff --git a/test/browser/BrowserCommands.ts b/test/browser/BrowserCommands.ts index 9bcd32d..3dadc3a 100644 --- a/test/browser/BrowserCommands.ts +++ b/test/browser/BrowserCommands.ts @@ -1,4 +1,4 @@ -import { browser } from '@wdio/globals'; +import {browser} from '@wdio/globals'; export enum DocumentReadyState { Loading = 'Loading', @@ -7,9 +7,9 @@ export enum DocumentReadyState { } export async function waitUntilDocumentReadyState(waitForReadyState: DocumentReadyState): Promise { - await browser.waitUntil(() => browser.execute(`document.readyState === "${DocumentReadyState[waitForReadyState]}"`) as Promise, { + await browser.waitUntil(() => browser.execute(`document.readyState === "${DocumentReadyState[waitForReadyState]}"`) as Promise, { timeout: 10000, timeoutMsg: `Document did not have a readyState of [${waitForReadyState}] after [10] seconds`, interval: 1000 }); -} +} diff --git a/test/config/Browserstack/BrowserstackApi.ts b/test/config/Browserstack/BrowserstackApi.ts index ec2633c..3e07d88 100644 --- a/test/config/Browserstack/BrowserstackApi.ts +++ b/test/config/Browserstack/BrowserstackApi.ts @@ -1,27 +1,27 @@ -import SupportedBrowser from "./SupportedBrowser"; +import SupportedBrowser from './SupportedBrowser'; // Source: https://github.com/browserstack/api export class BrowserstackApi { - private readonly _baseUrl: string = 'https://api.browserstack.com/5'; - private readonly _authorizationHeader: string; + private readonly _baseUrl: string = 'https://api.browserstack.com/5'; + private readonly _authorizationHeader: string; - constructor(username: string, accessKey: string) { - this._authorizationHeader = `Basic ${Buffer.from(`${username}:${accessKey}`).toString('base64')}`; + constructor(username: string, accessKey: string) { + this._authorizationHeader = `Basic ${Buffer.from(`${username}:${accessKey}`).toString('base64')}`; + } + + public async getSupportedBrowsers(): Promise { + const endpoint = `${this._baseUrl}/browsers?flat=true`; + const headers = { + Authorization: this._authorizationHeader + }; + + const response = await fetch(endpoint, {headers}); + + if (!response.ok) { + throw new Error(`Failed to fetch BrowserStack supported browsers due to [ ${response.statusText}]`); } - public async getSupportedBrowsers(): Promise { - const endpoint = `${this._baseUrl}/browsers?flat=true`; - const headers = { - 'Authorization': this._authorizationHeader - }; - - const response = await fetch(endpoint, { headers }); - - if (!response.ok) { - throw new Error(`Failed to fetch BrowserStack supported browsers due to [ ${response.statusText}]`); - } - - return response.json() as Promise; - } -} \ No newline at end of file + return response.json() as Promise; + } +} diff --git a/test/config/Browserstack/SupportedBrowser.ts b/test/config/Browserstack/SupportedBrowser.ts index a14d572..1c487ab 100644 --- a/test/config/Browserstack/SupportedBrowser.ts +++ b/test/config/Browserstack/SupportedBrowser.ts @@ -1,10 +1,10 @@ type SupportedBrowser = { - os: string; - os_version: string; - browser: string; - device: string; - browser_version: string | null; - real_mobile: boolean; -} + os: string; + os_version: string; + browser: string; + device: string; + browser_version: string | null; + real_mobile: boolean; +}; -export default SupportedBrowser; \ No newline at end of file +export default SupportedBrowser; diff --git a/test/config/CommandLine.ts b/test/config/CommandLine.ts index 0c60b20..0d1f4a6 100644 --- a/test/config/CommandLine.ts +++ b/test/config/CommandLine.ts @@ -27,43 +27,61 @@ const defaultUseBrowserstack = false; const defaultUseBrowserstackLocal = false; export default class CommandLine { - private static readonly _program: Command = new Command(); + private static readonly _program: Command = new Command(); public static parse(args: string[]): CommandLineOptions { CommandLine._program.parse(args); - return CommandLine._program.opts(); + 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 { CommandLine._program.version(PackageJson.version); - + const setupProgramOptions = (): void => { const handleArrayOption = (value: string, previousValue: string[]) => { if (previousValue) { return previousValue.concat(value); } - + return [value]; }; - + // Required options CommandLine._program.requiredOption('--application-id ', 'The application ID to use'); CommandLine._program.requiredOption('--secret ', 'The secret to use'); CommandLine._program.requiredOption('--pcast-uri ', 'The pcast URI to use'); CommandLine._program.requiredOption('--channel-uri ', 'The channel URI to use'); - + CommandLine._program.option('--ingest-uri ', 'The ingest URI to use'); CommandLine._program.option('--publisher-uri ', 'The publisher URI to use'); - CommandLine._program.option('--viewer ', 'The browser and OS for simulating a viewer to use', handleArrayOption, defaultViewers); - CommandLine._program.option('--publisher ', 'The browser and OS for simulating a publisher to use', handleArrayOption, defaultPublishers); + CommandLine._program.option( + '--viewer ', + 'The browser and OS for simulating a viewer to use', + handleArrayOption, + defaultViewers + ); + CommandLine._program.option( + '--publisher ', + 'The browser and OS for simulating a publisher to use', + handleArrayOption, + defaultPublishers + ); CommandLine._program.option('-t, --test ', 'The test to run', handleArrayOption, defaultTests); 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('--browserstack-user ', 'The BrowserStack username to use', process.env.BROWSERSTACK_USER || ''); CommandLine._program.option('--browserstack-key ', 'The BrowserStack key to use', process.env.BROWSERSTACK_KEY || ''); CommandLine._program.option('--log-level ', 'The log level to use', LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(defaultLogLevel)); - } + }; setupProgramOptions(); } diff --git a/test/config/TestConfiguration.ts b/test/config/TestConfiguration.ts index c63a656..79c29bd 100644 --- a/test/config/TestConfiguration.ts +++ b/test/config/TestConfiguration.ts @@ -1,78 +1,77 @@ -import CommandLine, { CommandLineOptions } from './CommandLine'; - +import type {CommandLineOptions} from './CommandLine'; type ApplicationCredentials = { - applicationId: string; - secret: string; -} + applicationId: string; + secret: string; +}; type Uri = { - pcast: string; - ingest: string | undefined; - channel: string | undefined; - publisher: string | undefined; -} + pcast: string; + ingest: string | undefined; + channel: string | undefined; + publisher: string | undefined; +}; type BrowserstackConfiguration = { - enabled: boolean; - local: boolean; - user: string; - key: string; -} + enabled: boolean; + local: boolean; + user: string; + key: string; +}; export default class TestConfiguration { - private readonly _applicationCredentials: ApplicationCredentials; - private readonly _uri: Uri; - private readonly _viewers: string[]; - private readonly _publishers: string[]; - private readonly _tests: string[]; - private readonly _browserstack: BrowserstackConfiguration; + private readonly _applicationCredentials: ApplicationCredentials; + private readonly _uri: Uri; + private readonly _viewers: string[]; + private readonly _publishers: string[]; + private readonly _tests: string[]; + private readonly _browserstack: BrowserstackConfiguration; - constructor(commandLineOptions: CommandLineOptions) { - this._applicationCredentials = { - applicationId: commandLineOptions.applicationId, - secret: commandLineOptions.secret, - }; + constructor(commandLineOptions: CommandLineOptions) { + this._applicationCredentials = { + applicationId: commandLineOptions.applicationId, + secret: commandLineOptions.secret + }; - this._uri = { - pcast: commandLineOptions.pcastUri, - ingest: commandLineOptions.ingestUri, - channel: commandLineOptions.channelUri, - publisher: commandLineOptions.publisherUri, - }; + this._uri = { + pcast: commandLineOptions.pcastUri, + ingest: commandLineOptions.ingestUri, + channel: commandLineOptions.channelUri, + publisher: commandLineOptions.publisherUri + }; - this._viewers = commandLineOptions.viewers; - this._publishers = commandLineOptions.publishers; - this._tests = commandLineOptions.tests; - this._browserstack = { - enabled: commandLineOptions.useBrowserstack, - local: commandLineOptions.useBrowserstackLocal, - user: commandLineOptions.browserstackUser, - key: commandLineOptions.browserstackKey, - }; - } + this._viewers = commandLineOptions.viewers; + this._publishers = commandLineOptions.publishers; + this._tests = commandLineOptions.tests; + this._browserstack = { + enabled: commandLineOptions.useBrowserstack, + local: commandLineOptions.useBrowserstackLocal, + user: commandLineOptions.browserstackUser, + key: commandLineOptions.browserstackKey + }; + } - get applicationCredentials(): ApplicationCredentials { - return this._applicationCredentials; - } + get applicationCredentials(): ApplicationCredentials { + return this._applicationCredentials; + } - get uri(): Uri { - return this._uri; - } + get uri(): Uri { + return this._uri; + } - get viewers(): string[] { - return this._viewers; - } + get viewers(): string[] { + return this._viewers; + } - get publishers(): string[] { - return this._publishers; - } + get publishers(): string[] { + return this._publishers; + } - get tests(): string[] { - return this._tests; - } + get tests(): string[] { + return this._tests; + } - get browserstack(): BrowserstackConfiguration { - return this._browserstack; - } -} \ No newline at end of file + get browserstack(): BrowserstackConfiguration { + return this._browserstack; + } +} diff --git a/test/logger/Logger.ts b/test/logger/Logger.ts index e339caf..198a0e8 100644 --- a/test/logger/Logger.ts +++ b/test/logger/Logger.ts @@ -65,7 +65,7 @@ export class Logger { const timestamp = new Date().toISOString(); const formattedMessage = this.formatMessage(message, ...optionalParameters); const levelString = LoggingLevelMapping.convertLoggingLevelToLoggingLevelType(level); - + // Safely call appenders with error handling this._appenders.forEach(appender => { try { @@ -86,22 +86,23 @@ export class Logger { // More efficient parameter substitution let result = message; let paramIndex = 0; - + // Replace all {} placeholders with parameters while (result.includes('{}') && paramIndex < optionalParameters.length) { const paramString = this.parameterToString(optionalParameters[paramIndex]); result = result.replace('{}', paramString); paramIndex++; } - + // Append remaining parameters if any if (paramIndex < optionalParameters.length) { - const remainingParams = optionalParameters.slice(paramIndex) + const remainingParams = optionalParameters + .slice(paramIndex) .map(param => this.parameterToString(param)) .join(' '); result += ` ${remainingParams}`; } - + return result; } diff --git a/test/logger/LoggerFactory.ts b/test/logger/LoggerFactory.ts index fbdc1d7..a56c422 100644 --- a/test/logger/LoggerFactory.ts +++ b/test/logger/LoggerFactory.ts @@ -1,20 +1,20 @@ -import { ConsoleAppender } from './appenders/ConsoleAppender'; +import {ConsoleAppender} from './appenders/ConsoleAppender'; import IAppender from './appenders/IAppender'; import {Logger} from './Logger'; -import {LoggingLevel, LoggingLevelMapping, LoggingLevelType} from './LoggingLevel'; +import {LoggingLevel, LoggingLevelMapping} from './LoggingLevel'; import {Threshold} from './Threshold'; export default class LoggerFactory { private static readonly _loggerForCategory: Map = new Map(); private static readonly _threshold: {level: LoggingLevel} = {level: LoggingLevel.Info}; - private static readonly _appenders: IAppender[] = [new ConsoleAppender()]; + private static readonly _appenders: IAppender[] = [new ConsoleAppender()]; public static getLogger(category: string): Logger { let logger = LoggerFactory._loggerForCategory.get(category); if (logger === undefined) { logger = new Logger({category, threshold: new Threshold(LoggerFactory._threshold), appenders: LoggerFactory._appenders}); - + LoggerFactory._loggerForCategory.set(category, logger); } @@ -29,4 +29,4 @@ export default class LoggerFactory { private constructor() { throw new Error('[LoggerFactory] is a static class that may not be instantiated'); } -} \ No newline at end of file +} diff --git a/test/logger/appenders/ConsoleAppender.ts b/test/logger/appenders/ConsoleAppender.ts index 98fe9d2..274d1dc 100644 --- a/test/logger/appenders/ConsoleAppender.ts +++ b/test/logger/appenders/ConsoleAppender.ts @@ -12,19 +12,19 @@ export class ConsoleAppender implements IAppender { console.info(`${timestamp} [${category}] ${message}`); break; - case LoggingLevel.Error: + case LoggingLevel.Error: console.error(`${timestamp} [${category}] ${message}`); break; - case LoggingLevel.Fatal: + case LoggingLevel.Fatal: console.error(`${timestamp} [${category}] ${message}`); break; - case LoggingLevel.Off: - case LoggingLevel.All: + case LoggingLevel.Off: + case LoggingLevel.All: break; - default: + default: assertUnreachable(level); } } diff --git a/test/pages/Page.ts b/test/pages/Page.ts index 6dfe3ce..0474cb4 100644 --- a/test/pages/Page.ts +++ b/test/pages/Page.ts @@ -1,33 +1,33 @@ - import {browser} from '@wdio/globals'; +import {browser} from '@wdio/globals'; +export type PageOptions = { + browser?: typeof browser; // MultiRemote usecase +}; - export type PageOptions = { - browser?: typeof browser; // MultiRemote usecase +export type PageOpenOptions = { + queryParameters?: Record; + isNewTabRequest?: boolean; + endpoint?: string; + requestPath?: string; +}; - } +export default class Page { + private readonly _baseUrl: string; - export type PageOpenOptions = { - queryParameters?: Record; - isNewTabRequest?: boolean; - endpoint?: string; - requestPath?: string; - }; - - export default class Page { - private readonly _baseUrl: string; - - constructor(baseUrl: string) { + constructor(baseUrl: string) { this._baseUrl = baseUrl; } - public async open(options: PageOpenOptions = {}): Promise { + public async open(options: PageOpenOptions = {}): Promise { 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) { await browser.newWindow(pageUrl); - } else { - await browser.url(pageUrl); - } + } else { + await browser.url(pageUrl); } } +} diff --git a/test/pages/Subscribing.page.ts b/test/pages/Subscribing.page.ts index f741606..8ce9313 100644 --- a/test/pages/Subscribing.page.ts +++ b/test/pages/Subscribing.page.ts @@ -1,6 +1,6 @@ -import Page, { PageOpenOptions } from './Page.ts'; +import Page, {PageOpenOptions} from './Page.ts'; -export type SubscribingPageOptions = { }; +export type SubscribingPageOptions = {}; export class SubscribingPage extends Page { constructor(baseUri: string, options: SubscribingPageOptions) { @@ -12,7 +12,6 @@ export class SubscribingPage extends Page { } public async open(options?: PageOpenOptions): Promise { - await super.open(options); + await super.open(options); } - } diff --git a/test/runner/TestRunner.ts b/test/runner/TestRunner.ts index 1f7a736..02b190f 100644 --- a/test/runner/TestRunner.ts +++ b/test/runner/TestRunner.ts @@ -1,6 +1,6 @@ import LoggerFactory from '../logger/LoggerFactory'; import CommandLine from '../config/CommandLine'; -import { CommandLineOptions } from '../config/CommandLine'; +import {CommandLineOptions} from '../config/CommandLine'; export default class TestRunner { private static readonly _logger = LoggerFactory.getLogger('TestRunner'); @@ -16,4 +16,4 @@ export default class TestRunner { TestRunner._logger.info('TestRunner started'); TestRunner._logger.info(JSON.stringify(TestRunner._commandLineOptions, null, 2)); } -} \ No newline at end of file +} diff --git a/test/workflows/SubscribeWorkflow.test.ts b/test/workflows/SubscribeWorkflow.test.ts deleted file mode 100644 index d6580fa..0000000 --- a/test/workflows/SubscribeWorkflow.test.ts +++ /dev/null @@ -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'); - }); - }); -}); diff --git a/test/workflows/factories/TestContextFactory.ts b/test/workflows/factories/TestContextFactory.ts deleted file mode 100644 index 255628d..0000000 --- a/test/workflows/factories/TestContextFactory.ts +++ /dev/null @@ -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 { - this.cleanup.push(task); - }, - executeCleanup: async function(): Promise { - 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: [], - }, - }; - } -} diff --git a/test/workflows/interfaces/ITestContext.ts b/test/workflows/interfaces/ITestContext.ts deleted file mode 100644 index 0dc626b..0000000 --- a/test/workflows/interfaces/ITestContext.ts +++ /dev/null @@ -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)[]; - - addCleanupTask(task: () => Promise): void; - executeCleanup(): Promise; -} - -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[]; - }; -} diff --git a/test/workflows/services/ChannelService.ts b/test/workflows/services/ChannelService.ts deleted file mode 100644 index 478cb3b..0000000 --- a/test/workflows/services/ChannelService.ts +++ /dev/null @@ -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 { - 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; - } - } -} diff --git a/test/workflows/services/TokenService.ts b/test/workflows/services/TokenService.ts deleted file mode 100644 index b16e466..0000000 --- a/test/workflows/services/TokenService.ts +++ /dev/null @@ -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}`; - } - } -} diff --git a/test/workflows/strategies/StreamKindStrategy.ts b/test/workflows/strategies/StreamKindStrategy.ts deleted file mode 100644 index ad6c461..0000000 --- a/test/workflows/strategies/StreamKindStrategy.ts +++ /dev/null @@ -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}`); - } - } -}