-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add discovery-polling command (#634)
Co-authored-by: Or Rubin <or.rubin@hotmail.com>
- Loading branch information
1 parent
936cd28
commit e58e2d1
Showing
17 changed files
with
525 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { RestDiscoveryOptions } from 'src/Discovery'; | ||
import { DiscoveryPollingFactory } from 'src/Discovery/DiscoveryPollingFactory'; | ||
import { ErrorMessageFactory, logger } from 'src/Utils'; | ||
import { container } from 'tsyringe'; | ||
import { Arguments, Argv, CommandModule } from 'yargs'; | ||
|
||
export class PollingDiscoveryStatus implements CommandModule { | ||
public readonly command = 'discovery:polling [options] <discoveryId>'; | ||
public readonly describe = | ||
'Allows to configure a polling of discovery status.'; | ||
|
||
public builder(argv: Argv): Argv { | ||
return argv | ||
.option('token', { | ||
alias: 't', | ||
describe: 'Bright API-key', | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('project', { | ||
alias: 'p', | ||
describe: 'ID of the project', | ||
string: true, | ||
requiresArg: true, | ||
demandOption: true | ||
}) | ||
.option('interval', { | ||
requiresArg: true, | ||
describe: | ||
'The sampling interval between status checks. ' + | ||
'Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count.', | ||
default: 5000 | ||
}) | ||
.option('timeout', { | ||
requiresArg: true, | ||
describe: | ||
'Period of time between the end of a timeout period or completion of a discovery status request, and the next request for status. ' + | ||
'Eg: 60, "2min", "10h", "7d". A numeric value is interpreted as a milliseconds count.' | ||
}) | ||
.positional('discoveryId', { | ||
describe: 'ID of an existing discovery.', | ||
demandOption: true, | ||
type: 'string' | ||
}) | ||
.middleware((args: Arguments) => | ||
container.register<RestDiscoveryOptions>(RestDiscoveryOptions, { | ||
useValue: { | ||
insecure: args.insecure as boolean, | ||
baseURL: args.api as string, | ||
apiKey: args.token as string, | ||
proxyURL: (args.proxyBright ?? args.proxy) as string, | ||
timeout: args.timeout as number | ||
} | ||
}) | ||
); | ||
} | ||
|
||
public async handler(args: Arguments): Promise<void> { | ||
try { | ||
const pollingFactory = container.resolve<DiscoveryPollingFactory>( | ||
DiscoveryPollingFactory | ||
); | ||
const polling = pollingFactory.create({ | ||
discoveryId: args.discoveryId as string, | ||
projectId: args.project as string, | ||
timeout: args.timeout as number, | ||
interval: args.interval as number | ||
}); | ||
|
||
await polling.start(); | ||
|
||
process.exit(0); | ||
} catch (error) { | ||
logger.error( | ||
ErrorMessageFactory.genericCommandError({ | ||
error, | ||
command: 'discovery:polling' | ||
}) | ||
); | ||
process.exit(1); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { | ||
DiscoveryPollingConfig, | ||
DiscoveryPollingFactory | ||
} from './DiscoveryPollingFactory'; | ||
import { Polling } from '../Utils/Polling'; | ||
import { DiscoveryPolling } from './DiscoveryPolling'; | ||
import { Discoveries } from './Discoveries'; | ||
import { inject, injectable } from 'tsyringe'; | ||
|
||
@injectable() | ||
export class DefaultDiscoveryPollingFactory implements DiscoveryPollingFactory { | ||
constructor( | ||
@inject(Discoveries) | ||
private readonly discoveries: Discoveries | ||
) {} | ||
|
||
public create(options: DiscoveryPollingConfig): Polling { | ||
return new DiscoveryPolling(options, this.discoveries); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
import 'reflect-metadata'; | ||
import { Logger, logger } from '../Utils'; | ||
import { DiscoveryPollingConfig } from './DiscoveryPollingFactory'; | ||
import { DiscoveryView, DiscoveryStatus } from './DiscoveryView'; | ||
import { Discoveries } from './Discoveries'; | ||
import { DiscoveryPolling } from './DiscoveryPolling'; | ||
import { instance, mock, reset, spy, verify, when } from 'ts-mockito'; | ||
import { setTimeout } from 'node:timers/promises'; | ||
|
||
describe('DiscoveryPolling', () => { | ||
const discoveryId = 'hAXZjjahZqpvgK3yNEdp6t'; | ||
const projectId = 'hADZjiahZqpvgK3yNEdp8b'; | ||
|
||
const firstResponse: DiscoveryView = { | ||
id: discoveryId, | ||
name: 'some name', | ||
status: DiscoveryStatus.RUNNING | ||
}; | ||
|
||
const discoveryManagerMock = mock<Discoveries>(); | ||
|
||
let loggerSpy!: Logger; | ||
|
||
beforeEach(() => { | ||
loggerSpy = spy(logger); | ||
}); | ||
|
||
afterEach(() => { | ||
reset<Discoveries | Logger>(discoveryManagerMock, loggerSpy); | ||
}); | ||
|
||
describe('constructor', () => { | ||
it('should warn if timeout is not specified', () => { | ||
// arrange | ||
const options: DiscoveryPollingConfig = { | ||
projectId, | ||
discoveryId | ||
}; | ||
|
||
// act | ||
new DiscoveryPolling(options, instance(discoveryManagerMock)); | ||
|
||
// assert | ||
verify( | ||
loggerSpy.warn( | ||
`Warning: It looks like you've been running polling without "timeout" option.` | ||
) | ||
).once(); | ||
verify( | ||
loggerSpy.warn( | ||
`The recommended way to install polling with a minimal timeout: 10-20min.` | ||
) | ||
).once(); | ||
}); | ||
|
||
it('should warn if interval is less than 10s', () => { | ||
// arrange | ||
const options = { | ||
discoveryId, | ||
projectId, | ||
interval: 5000 | ||
}; | ||
|
||
// act | ||
new DiscoveryPolling(options, instance(discoveryManagerMock)); | ||
|
||
// assert | ||
verify( | ||
loggerSpy.warn( | ||
`Warning: The minimal value for polling interval is 10 seconds.` | ||
) | ||
).once(); | ||
}); | ||
}); | ||
|
||
describe('start', () => { | ||
const options: DiscoveryPollingConfig = { | ||
discoveryId, | ||
projectId, | ||
interval: 1 | ||
}; | ||
const spiedOptions = spy(options); | ||
|
||
let sut!: DiscoveryPolling; | ||
|
||
beforeEach(() => { | ||
sut = new DiscoveryPolling(options, instance(discoveryManagerMock)); | ||
}); | ||
|
||
afterEach(() => reset(spiedOptions)); | ||
|
||
it.each([ | ||
DiscoveryStatus.DONE, | ||
DiscoveryStatus.DISRUPTED, | ||
DiscoveryStatus.FAILED, | ||
DiscoveryStatus.STOPPED | ||
])( | ||
'should start polling and stop on discovery status changed to "%s"', | ||
async (status) => { | ||
// arrange | ||
when(discoveryManagerMock.get(projectId, discoveryId)) | ||
.thenResolve(firstResponse) | ||
.thenResolve({ ...firstResponse, status }); | ||
|
||
// act | ||
await sut.start(); | ||
|
||
// assert | ||
verify(discoveryManagerMock.get(projectId, discoveryId)).twice(); | ||
verify( | ||
loggerSpy.log( | ||
`The discovery has been finished with status: ${status}.` | ||
) | ||
).once(); | ||
} | ||
); | ||
|
||
it('should start polling and stop on timeout', async () => { | ||
// arrange | ||
const timeout = 1500; | ||
const interval = 1000; | ||
when(spiedOptions.timeout).thenReturn(timeout); | ||
when(spiedOptions.interval).thenReturn(interval); | ||
|
||
when(discoveryManagerMock.get(projectId, discoveryId)).thenResolve( | ||
firstResponse | ||
); | ||
|
||
// act | ||
jest.useFakeTimers(); | ||
const promise = sut.start(); | ||
await setTimeout(10); | ||
jest.runAllTimers(); | ||
await promise; | ||
jest.useRealTimers(); | ||
|
||
// assert | ||
verify(discoveryManagerMock.get(projectId, discoveryId)).once(); | ||
verify(loggerSpy.log('Polling has been stopped by timeout.')).once(); | ||
}); | ||
}); | ||
|
||
describe('stop', () => { | ||
it('should stop polling', async () => { | ||
// arrange | ||
const sut = new DiscoveryPolling( | ||
{ | ||
projectId, | ||
discoveryId, | ||
interval: 1000 | ||
}, | ||
instance(discoveryManagerMock) | ||
); | ||
|
||
when(discoveryManagerMock.get(projectId, discoveryId)).thenResolve( | ||
firstResponse | ||
); | ||
|
||
// act | ||
const start = sut.start(); | ||
await setTimeout(10); | ||
await sut.stop(); | ||
await start; | ||
|
||
// assert | ||
verify(discoveryManagerMock.get(projectId, discoveryId)).once(); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.