mirror of
https://github.com/LightZirconite/Microsoft-Rewards-Bot.git
synced 2026-01-10 17:26:17 +00:00
feat: Refactor and modularize flow handling for improved maintainability
- Extracted BuyModeHandler, DesktopFlow, MobileFlow, and SummaryReporter into separate modules for better organization and testability. - Enhanced type safety and added interfaces for various return types in Load, Logger, UserAgent, and flow modules. - Implemented comprehensive error handling and logging throughout the new modules. - Added unit tests for DesktopFlow, MobileFlow, and SummaryReporter to ensure functionality and correctness. - Updated existing utility functions to support new flow structures and improve code clarity.
This commit is contained in:
73
tests/flows/desktopFlow.test.ts
Normal file
73
tests/flows/desktopFlow.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
/**
|
||||
* DesktopFlow unit tests
|
||||
* Validates desktop automation flow logic
|
||||
*/
|
||||
|
||||
test('DesktopFlow module exports correctly', async () => {
|
||||
const { DesktopFlow } = await import('../../src/flows/DesktopFlow')
|
||||
assert.ok(DesktopFlow, 'DesktopFlow should be exported')
|
||||
assert.equal(typeof DesktopFlow, 'function', 'DesktopFlow should be a class constructor')
|
||||
})
|
||||
|
||||
test('DesktopFlow has run method', async () => {
|
||||
const { DesktopFlow } = await import('../../src/flows/DesktopFlow')
|
||||
|
||||
// Mock bot instance
|
||||
const mockBot = {
|
||||
log: () => {},
|
||||
isMobile: false,
|
||||
config: { workers: {}, runOnZeroPoints: false },
|
||||
browser: { func: {} },
|
||||
utils: {},
|
||||
activities: {},
|
||||
compromisedModeActive: false
|
||||
}
|
||||
|
||||
const flow = new DesktopFlow(mockBot as never)
|
||||
assert.ok(flow, 'DesktopFlow instance should be created')
|
||||
assert.equal(typeof flow.run, 'function', 'DesktopFlow should have run() method')
|
||||
})
|
||||
|
||||
test('DesktopFlowResult interface has correct structure', async () => {
|
||||
const { DesktopFlow } = await import('../../src/flows/DesktopFlow')
|
||||
|
||||
// Validate that DesktopFlowResult type exports (compile-time check)
|
||||
type DesktopFlowResult = Awaited<ReturnType<InstanceType<typeof DesktopFlow>['run']>>
|
||||
|
||||
const mockResult: DesktopFlowResult = {
|
||||
initialPoints: 1000,
|
||||
collectedPoints: 50
|
||||
}
|
||||
|
||||
assert.equal(typeof mockResult.initialPoints, 'number', 'initialPoints should be a number')
|
||||
assert.equal(typeof mockResult.collectedPoints, 'number', 'collectedPoints should be a number')
|
||||
})
|
||||
|
||||
test('DesktopFlow handles security compromise mode', async () => {
|
||||
const { DesktopFlow } = await import('../../src/flows/DesktopFlow')
|
||||
|
||||
const logs: string[] = []
|
||||
const mockBot = {
|
||||
log: (_: boolean, __: string, message: string) => logs.push(message),
|
||||
isMobile: false,
|
||||
config: {
|
||||
workers: {},
|
||||
runOnZeroPoints: false,
|
||||
sessionPath: './sessions'
|
||||
},
|
||||
browser: { func: {} },
|
||||
utils: {},
|
||||
activities: {},
|
||||
workers: {},
|
||||
compromisedModeActive: true,
|
||||
compromisedReason: 'test-security-check'
|
||||
}
|
||||
|
||||
const flow = new DesktopFlow(mockBot as never)
|
||||
|
||||
// Note: Full test requires mocked browser context
|
||||
assert.ok(flow, 'DesktopFlow should handle compromised mode')
|
||||
})
|
||||
76
tests/flows/mobileFlow.test.ts
Normal file
76
tests/flows/mobileFlow.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
/**
|
||||
* MobileFlow unit tests
|
||||
* Validates mobile automation flow logic
|
||||
*/
|
||||
|
||||
test('MobileFlow module exports correctly', async () => {
|
||||
const { MobileFlow } = await import('../../src/flows/MobileFlow')
|
||||
assert.ok(MobileFlow, 'MobileFlow should be exported')
|
||||
assert.equal(typeof MobileFlow, 'function', 'MobileFlow should be a class constructor')
|
||||
})
|
||||
|
||||
test('MobileFlow has run method', async () => {
|
||||
const { MobileFlow } = await import('../../src/flows/MobileFlow')
|
||||
|
||||
// Mock bot instance
|
||||
const mockBot = {
|
||||
log: () => {},
|
||||
isMobile: true,
|
||||
config: {
|
||||
workers: {},
|
||||
runOnZeroPoints: false,
|
||||
searchSettings: { retryMobileSearchAmount: 0 }
|
||||
},
|
||||
browser: { func: {} },
|
||||
utils: {},
|
||||
activities: {},
|
||||
compromisedModeActive: false
|
||||
}
|
||||
|
||||
const flow = new MobileFlow(mockBot as never)
|
||||
assert.ok(flow, 'MobileFlow instance should be created')
|
||||
assert.equal(typeof flow.run, 'function', 'MobileFlow should have run() method')
|
||||
})
|
||||
|
||||
test('MobileFlowResult interface has correct structure', async () => {
|
||||
const { MobileFlow } = await import('../../src/flows/MobileFlow')
|
||||
|
||||
// Validate that MobileFlowResult type exports (compile-time check)
|
||||
type MobileFlowResult = Awaited<ReturnType<InstanceType<typeof MobileFlow>['run']>>
|
||||
|
||||
const mockResult: MobileFlowResult = {
|
||||
initialPoints: 1000,
|
||||
collectedPoints: 30
|
||||
}
|
||||
|
||||
assert.equal(typeof mockResult.initialPoints, 'number', 'initialPoints should be a number')
|
||||
assert.equal(typeof mockResult.collectedPoints, 'number', 'collectedPoints should be a number')
|
||||
})
|
||||
|
||||
test('MobileFlow accepts retry tracker', async () => {
|
||||
const { MobileFlow } = await import('../../src/flows/MobileFlow')
|
||||
const { MobileRetryTracker } = await import('../../src/util/MobileRetryTracker')
|
||||
|
||||
const mockBot = {
|
||||
log: () => {},
|
||||
isMobile: true,
|
||||
config: {
|
||||
workers: {},
|
||||
runOnZeroPoints: false,
|
||||
searchSettings: { retryMobileSearchAmount: 3 }
|
||||
},
|
||||
browser: { func: {} },
|
||||
utils: {},
|
||||
activities: {},
|
||||
compromisedModeActive: false
|
||||
}
|
||||
|
||||
const flow = new MobileFlow(mockBot as never)
|
||||
const tracker = new MobileRetryTracker(3)
|
||||
|
||||
assert.ok(flow, 'MobileFlow should accept retry tracker')
|
||||
assert.equal(typeof tracker.registerFailure, 'function', 'MobileRetryTracker should have registerFailure method')
|
||||
})
|
||||
80
tests/flows/summaryReporter.test.ts
Normal file
80
tests/flows/summaryReporter.test.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
/**
|
||||
* SummaryReporter unit tests
|
||||
* Validates reporting and notification logic
|
||||
*/
|
||||
|
||||
test('SummaryReporter module exports correctly', async () => {
|
||||
const { SummaryReporter } = await import('../../src/flows/SummaryReporter')
|
||||
assert.ok(SummaryReporter, 'SummaryReporter should be exported')
|
||||
assert.equal(typeof SummaryReporter, 'function', 'SummaryReporter should be a class constructor')
|
||||
})
|
||||
|
||||
test('SummaryReporter creates instance with config', async () => {
|
||||
const { SummaryReporter } = await import('../../src/flows/SummaryReporter')
|
||||
|
||||
const mockConfig = {
|
||||
webhook: { enabled: false },
|
||||
ntfy: { enabled: false },
|
||||
sessionPath: './sessions',
|
||||
jobState: { enabled: false }
|
||||
}
|
||||
|
||||
const reporter = new SummaryReporter(mockConfig as never)
|
||||
assert.ok(reporter, 'SummaryReporter instance should be created')
|
||||
})
|
||||
|
||||
test('SummaryReporter creates summary correctly', async () => {
|
||||
const { SummaryReporter } = await import('../../src/flows/SummaryReporter')
|
||||
|
||||
const mockConfig = {
|
||||
webhook: { enabled: false },
|
||||
ntfy: { enabled: false },
|
||||
sessionPath: './sessions',
|
||||
jobState: { enabled: false }
|
||||
}
|
||||
|
||||
const reporter = new SummaryReporter(mockConfig as never)
|
||||
|
||||
const accounts = [
|
||||
{ email: 'test@example.com', pointsEarned: 100, runDuration: 60000 },
|
||||
{ email: 'test2@example.com', pointsEarned: 150, runDuration: 70000, errors: ['test error'] }
|
||||
]
|
||||
|
||||
const startTime = new Date('2025-01-01T10:00:00Z')
|
||||
const endTime = new Date('2025-01-01T10:05:00Z')
|
||||
|
||||
const summary = reporter.createSummary(accounts, startTime, endTime)
|
||||
|
||||
assert.equal(summary.totalPoints, 250, 'Total points should be 250')
|
||||
assert.equal(summary.successCount, 1, 'Success count should be 1')
|
||||
assert.equal(summary.failureCount, 1, 'Failure count should be 1')
|
||||
assert.equal(summary.accounts.length, 2, 'Should have 2 accounts')
|
||||
})
|
||||
|
||||
test('SummaryData structure is correct', async () => {
|
||||
const { SummaryReporter } = await import('../../src/flows/SummaryReporter')
|
||||
|
||||
const mockConfig = {
|
||||
webhook: { enabled: false },
|
||||
ntfy: { enabled: false },
|
||||
sessionPath: './sessions',
|
||||
jobState: { enabled: false }
|
||||
}
|
||||
|
||||
const reporter = new SummaryReporter(mockConfig as never)
|
||||
|
||||
const summary = reporter.createSummary(
|
||||
[{ email: 'test@example.com', pointsEarned: 50, runDuration: 30000 }],
|
||||
new Date(),
|
||||
new Date()
|
||||
)
|
||||
|
||||
assert.ok(summary.startTime instanceof Date, 'startTime should be a Date')
|
||||
assert.ok(summary.endTime instanceof Date, 'endTime should be a Date')
|
||||
assert.equal(typeof summary.totalPoints, 'number', 'totalPoints should be a number')
|
||||
assert.equal(typeof summary.successCount, 'number', 'successCount should be a number')
|
||||
assert.ok(Array.isArray(summary.accounts), 'accounts should be an array')
|
||||
})
|
||||
Reference in New Issue
Block a user