From 4b672f231f1ececa718267c4a547002e8257243a Mon Sep 17 00:00:00 2001 From: Alexander Zinn Date: Mon, 18 Aug 2025 17:54:04 -0400 Subject: [PATCH] Add command line parsing and configuration management * Introduced `CommandLine` class for handling command line options and arguments * Added `TestConfiguration` class to manage application credentials and URIs * Implemented example usage for generating command line arguments from a configuration object * Updated `package.json` to include `commander` dependency * Removed unused `TestRunner` class and created a new `TestRunner` for executing tests with command line options * Adjusted logging level type to use lowercase values for consistency --- package.json | 3 +- test/config/CommandLine.ts | 140 +++++++++++++++++++++++++++++++ test/config/TestConfiguration.ts | 78 +++++++++++++++++ test/config/TestRunner.ts | 4 - test/config/example-usage.ts | 43 ++++++++++ test/logger/LoggingLevel.ts | 18 ++-- test/runner/TestRunner.ts | 23 +++++ 7 files changed, 295 insertions(+), 14 deletions(-) delete mode 100644 test/config/TestRunner.ts create mode 100644 test/config/example-usage.ts create mode 100644 test/runner/TestRunner.ts diff --git a/package.json b/package.json index 4bdb177..a5a0c24 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@techniker-me/pcast-api": "2025.0.2", - "@techniker-me/rtmp-push": "2025.0.2" + "@techniker-me/rtmp-push": "2025.0.2", + "commander": "14.0.0" } } diff --git a/test/config/CommandLine.ts b/test/config/CommandLine.ts index eacd618..fd873ed 100644 --- a/test/config/CommandLine.ts +++ b/test/config/CommandLine.ts @@ -1,4 +1,144 @@ +import {Command} from 'commander'; +import PackageJson from '../../package.json' assert {type: 'json'}; +import {LoggingLevel, LoggingLevelMapping} from '../logger/LoggingLevel'; + +export interface CommandLineOptions { + applicationId: string; + secret: string; + logLevel: string; + pcastUri: string; + ingestUri: string; + channelUri: string; + publisherUri: string; + viewers: string[]; + publishers: string[]; + tests: string[]; + useBrowserstack: boolean; + useBrowserstackLocal: boolean; + browserstackUser: string; + browserstackKey: string; +} + +interface ConfigurationObject { + viewers: string[]; + publishers: string[]; + tests: string[]; + useBrowserstack: boolean; + useBrowserstackLocal: boolean; + browserstackUser: string; + browserstackKey: string; + logLevel: string; + applicationId: string; + secret: string; + pcastUri: string; + ingestUri: string; + channelUri: string; + publisherUri?: string; +} + +const defaultLogLevel = LoggingLevel.Info; +const defaultViewers: string[] = []; +const defaultPublishers: string[] = []; +const defaultTests: string[] = []; +const defaultUseBrowserstack = false; +const defaultUseBrowserstackLocal = false; + export default class CommandLine { + private static readonly _program: Command = new Command(); + + public static parse(args: string[]): CommandLineOptions { + CommandLine._program.parse(args); + + return CommandLine._program.opts(); + } + + /** + * Converts a configuration object to command line arguments array + */ + public static configToArgs(config: ConfigurationObject): string[] { + const args: string[] = []; + + // Required options + args.push('--application-id', config.applicationId); + args.push('--secret', config.secret); + args.push('--pcast-uri', config.pcastUri); + args.push('--channel-uri', config.channelUri); + + // Optional URI options + if (config.publisherUri) { + args.push('--publisher-uri', config.publisherUri); + } + if (config.ingestUri) { + args.push('--ingest-uri', config.ingestUri); + } + + // Browser and OS options (singular option names, multiple values) + config.viewers.forEach(viewer => { + args.push('--viewer', viewer); + }); + config.publishers.forEach(publisher => { + args.push('--publisher', publisher); + }); + + // Test options (singular option name, multiple values) + config.tests.forEach(test => { + args.push('--test', test); + }); + + // BrowserStack options + if (config.useBrowserstack) { + args.push('--use-browserstack'); + } + if (config.useBrowserstackLocal) { + args.push('--use-browserstack-local'); + } + if (config.browserstackUser) { + args.push('--browserstack-user', config.browserstackUser); + } + if (config.browserstackKey) { + args.push('--browserstack-key', config.browserstackKey); + } + + // Logging options + if (config.logLevel) { + args.push('--log-level', config.logLevel); + } + + return args; + } + + static { + CommandLine._program.version(PackageJson.version); + CommandLine._setupProgramOptions(); + } + + private static _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('-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)); + } + private constructor() { throw new Error('[CommandLine] is a static class that may not be instantiated'); } diff --git a/test/config/TestConfiguration.ts b/test/config/TestConfiguration.ts index e69de29..c63a656 100644 --- a/test/config/TestConfiguration.ts +++ b/test/config/TestConfiguration.ts @@ -0,0 +1,78 @@ +import CommandLine, { CommandLineOptions } from './CommandLine'; + + +type ApplicationCredentials = { + applicationId: string; + secret: string; +} + +type Uri = { + pcast: string; + ingest: string | undefined; + channel: string | undefined; + publisher: string | undefined; +} + +type BrowserstackConfiguration = { + 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; + + 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._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 uri(): Uri { + return this._uri; + } + + get viewers(): string[] { + return this._viewers; + } + + get publishers(): string[] { + return this._publishers; + } + + get tests(): string[] { + return this._tests; + } + + get browserstack(): BrowserstackConfiguration { + return this._browserstack; + } +} \ No newline at end of file diff --git a/test/config/TestRunner.ts b/test/config/TestRunner.ts deleted file mode 100644 index 630924f..0000000 --- a/test/config/TestRunner.ts +++ /dev/null @@ -1,4 +0,0 @@ -import PCastAPI from '@techniker-me/pcast-api'; -import RtmpPush from '@technniker-me/rtmp-push'; - -class TestRunner {} diff --git a/test/config/example-usage.ts b/test/config/example-usage.ts new file mode 100644 index 0000000..8dd30c9 --- /dev/null +++ b/test/config/example-usage.ts @@ -0,0 +1,43 @@ +import CommandLine from './CommandLine'; + +// Example configuration object matching your structure +const config = { + viewers: ["chrome", "firefox"], + publishers: [], + tests: ["test/tests/real-time", "test/tests/dash", "test/tests/hls"], + useBrowserstack: false, + useBrowserstackLocal: false, + browserstackUser: "", + browserstackKey: "", + logLevel: "Info", + applicationId: "phenixrts.com-alex.zinn", + secret: "AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg", + pcastUri: "https://pcast-stg.phenixrts.com", + ingestUri: "rtmp://ingest-stg.phenixrts.com:80/ingest", + channelUri: "https://pcast-stg.phenixrts.com/channel", +}; + +// Convert configuration to command line arguments +const args = CommandLine.configToArgs(config); + +console.log('Generated command line arguments:'); +console.log(args.join(' ')); + +console.log('\nThis generates the equivalent of:'); +console.log('--application-id phenixrts.com-alex.zinn \\'); +console.log('--secret AMAsDzr.dIuGMZ.Zu52Dt~MQvP!DZwYg \\'); +console.log('--pcast-uri https://pcast-stg.phenixrts.com \\'); +console.log('--channel-uri https://pcast-stg.phenixrts.com/channel \\'); +console.log('--ingest-uri rtmp://ingest-stg.phenixrts.com:80/ingest \\'); +console.log('--viewer chrome \\'); +console.log('--viewer firefox \\'); +console.log('--test test/tests/real-time \\'); +console.log('--test test/tests/dash \\'); +console.log('--test test/tests/hls \\'); +console.log('--log-level Info'); + +console.log('\nNote: Users can pass --viewer, --publisher, and -t multiple times to build arrays'); + +// You can now use these arguments with the CommandLine.parse method +// const options = CommandLine.parse(args); +// console.log('Parsed options:', options); diff --git a/test/logger/LoggingLevel.ts b/test/logger/LoggingLevel.ts index bf0d7d4..566777e 100644 --- a/test/logger/LoggingLevel.ts +++ b/test/logger/LoggingLevel.ts @@ -9,27 +9,27 @@ export enum LoggingLevel { All = 7 } -export type LoggingLevelType = 'Off' | 'Fatal' | 'Error' | 'Warning' | 'Info' | 'Debug' | 'Trace' | 'All'; +export type LoggingLevelType = 'off' | 'fatal' | 'error' | 'warning' | 'info' | 'debug' | 'trace' | 'all'; export class LoggingLevelMapping { public static convertLoggingLevelToLoggingLevelType(level: LoggingLevel): LoggingLevelType { switch (level) { case LoggingLevel.Off: - return 'Off'; + return 'off'; case LoggingLevel.Fatal: - return 'Fatal'; + return 'fatal'; case LoggingLevel.Error: - return 'Error'; + return 'error'; case LoggingLevel.Warning: - return 'Warning'; + return 'warning'; case LoggingLevel.Info: - return 'Info'; + return 'info'; case LoggingLevel.Debug: - return 'Debug'; + return 'debug'; case LoggingLevel.Trace: - return 'Trace'; + return 'trace'; case LoggingLevel.All: - return 'All'; + return 'all'; default: throw new Error(`[LoggingLevelMapping] Received unknown logging level [${level}]`); } diff --git a/test/runner/TestRunner.ts b/test/runner/TestRunner.ts new file mode 100644 index 0000000..9a693c7 --- /dev/null +++ b/test/runner/TestRunner.ts @@ -0,0 +1,23 @@ +import PCastAPI from '@techniker-me/pcast-api'; +import RtmpPush from '@technniker-me/rtmp-push'; +import CommandLine from '../config/CommandLine'; + +interface CommandLineOptions { + applicationId: string; + secret: string; + logLevel: string; +} + +class TestRunner { + private static readonly _commandLineOptions: CommandLineOptions = CommandLine.parse(process.argv); + + public static main(): void { + // const testRunner = new TestRunner(); + // testRunner.run(); + + console.log(TestRunner._commandLineOptions); + } +} + +TestRunner.main(); +