mirror of
https://github.com/freqtrade/freqtrade.git
synced 2024-11-15 20:53:58 +00:00
Merge branch 'freqtrade:develop' into develop
This commit is contained in:
commit
5f0a27d355
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
@ -318,6 +318,17 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
mypy freqtrade scripts tests
|
mypy freqtrade scripts tests
|
||||||
|
|
||||||
|
- name: Run Pester tests (PowerShell)
|
||||||
|
run: |
|
||||||
|
$PSVersionTable
|
||||||
|
Set-PSRepository psgallery -InstallationPolicy trusted
|
||||||
|
Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force
|
||||||
|
$Error.clear()
|
||||||
|
Invoke-Pester -Path "tests" -CI
|
||||||
|
if ($Error.Length -gt 0) {exit 1}
|
||||||
|
|
||||||
|
shell: powershell
|
||||||
|
|
||||||
- name: Discord notification
|
- name: Discord notification
|
||||||
uses: rjstone/discord-webhook-notify@v1
|
uses: rjstone/discord-webhook-notify@v1
|
||||||
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
|
||||||
|
|
|
@ -5,6 +5,30 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md)
|
||||||
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
|
||||||
Otherwise, please follow the instructions below.
|
Otherwise, please follow the instructions below.
|
||||||
|
|
||||||
|
All instructions assume that python 3.9+ is installed and available.
|
||||||
|
|
||||||
|
## Clone the git repository
|
||||||
|
|
||||||
|
First of all clone the repository by running:
|
||||||
|
|
||||||
|
``` powershell
|
||||||
|
git clone https://github.com/freqtrade/freqtrade.git
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, choose your installation method, either automatically via script (recommended) or manually following the corresponding instructions.
|
||||||
|
|
||||||
|
## Install freqtrade automatically
|
||||||
|
|
||||||
|
### Run the installation script
|
||||||
|
|
||||||
|
The script will ask you a few questions to determine which parts should be installed.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy Bypass
|
||||||
|
cd freqtrade
|
||||||
|
. .\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
## Install freqtrade manually
|
## Install freqtrade manually
|
||||||
|
|
||||||
!!! Note "64bit Python version"
|
!!! Note "64bit Python version"
|
||||||
|
@ -14,13 +38,7 @@ Otherwise, please follow the instructions below.
|
||||||
!!! Hint
|
!!! Hint
|
||||||
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information.
|
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information.
|
||||||
|
|
||||||
### 1. Clone the git repository
|
### Install ta-lib
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/freqtrade/freqtrade.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Install ta-lib
|
|
||||||
|
|
||||||
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
|
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
|
||||||
class RunMode(Enum):
|
class RunMode(str, Enum):
|
||||||
"""
|
"""
|
||||||
Bot running mode (backtest, hyperopt, ...)
|
Bot running mode (backtest, hyperopt, ...)
|
||||||
can be "live", "dry-run", "backtest", "edge", "hyperopt".
|
can be "live", "dry-run", "backtest", "edge", "hyperopt".
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
numpy==1.26.4
|
numpy==1.26.4
|
||||||
pandas==2.2.2
|
pandas==2.2.2
|
||||||
|
bottleneck==1.3.8
|
||||||
|
numexpr==2.10.0
|
||||||
pandas-ta==0.3.14b
|
pandas-ta==0.3.14b
|
||||||
|
|
||||||
ccxt==4.3.35
|
ccxt==4.3.35
|
||||||
|
|
285
setup.ps1
Normal file
285
setup.ps1
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
Clear-Host
|
||||||
|
|
||||||
|
$Timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
|
||||||
|
$Global:LogFilePath = Join-Path $env:TEMP "script_log_$Timestamp.txt"
|
||||||
|
|
||||||
|
$RequirementFiles = @("requirements.txt", "requirements-dev.txt", "requirements-hyperopt.txt", "requirements-freqai.txt", "requirements-freqai-rl.txt", "requirements-plot.txt")
|
||||||
|
$VenvName = ".venv"
|
||||||
|
$VenvDir = Join-Path $PSScriptRoot $VenvName
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param (
|
||||||
|
[string]$Message,
|
||||||
|
[string]$Level = 'INFO'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path $LogFilePath)) {
|
||||||
|
New-Item -ItemType File -Path $LogFilePath -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($Level) {
|
||||||
|
'INFO' { Write-Host $Message -ForegroundColor Green }
|
||||||
|
'WARNING' { Write-Host $Message -ForegroundColor Yellow }
|
||||||
|
'ERROR' { Write-Host $Message -ForegroundColor Red }
|
||||||
|
'PROMPT' { Write-Host $Message -ForegroundColor Cyan }
|
||||||
|
}
|
||||||
|
|
||||||
|
"${Level}: $Message" | Out-File $LogFilePath -Append
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-UserSelection {
|
||||||
|
param (
|
||||||
|
[string]$Prompt,
|
||||||
|
[string[]]$Options,
|
||||||
|
[string]$DefaultChoice = 'A',
|
||||||
|
[bool]$AllowMultipleSelections = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Log "$Prompt`n" -Level 'PROMPT'
|
||||||
|
for ($I = 0; $I -lt $Options.Length; $I++) {
|
||||||
|
Write-Log "$([char](65 + $I)). $($Options[$I])" -Level 'PROMPT'
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($AllowMultipleSelections) {
|
||||||
|
Write-Log "`nSelect one or more options by typing the corresponding letters, separated by commas." -Level 'PROMPT'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "`nSelect an option by typing the corresponding letter." -Level 'PROMPT'
|
||||||
|
}
|
||||||
|
|
||||||
|
[string]$UserInput = Read-Host
|
||||||
|
if ([string]::IsNullOrEmpty($UserInput)) {
|
||||||
|
$UserInput = $DefaultChoice
|
||||||
|
}
|
||||||
|
$UserInput = $UserInput.ToUpper()
|
||||||
|
|
||||||
|
if ($AllowMultipleSelections) {
|
||||||
|
$Selections = $UserInput.Split(',') | ForEach-Object { $_.Trim() }
|
||||||
|
$SelectedIndices = @()
|
||||||
|
foreach ($Selection in $Selections) {
|
||||||
|
if ($Selection -match '^[A-Z]$') {
|
||||||
|
$Index = [int][char]$Selection - [int][char]'A'
|
||||||
|
if ($Index -ge 0 -and $Index -lt $Options.Length) {
|
||||||
|
$SelectedIndices += $Index
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $Selection. Please enter letters within the valid range of options." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $Selection. Please enter a letter between A and Z." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $SelectedIndices
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($UserInput -match '^[A-Z]$') {
|
||||||
|
$SelectedIndex = [int][char]$UserInput - [int][char]'A'
|
||||||
|
if ($SelectedIndex -ge 0 -and $SelectedIndex -lt $Options.Length) {
|
||||||
|
return $SelectedIndex
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $UserInput. Please enter a letter within the valid range of options." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Invalid input: $UserInput. Please enter a letter between A and Z." -Level 'ERROR'
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Exit-Script {
|
||||||
|
param (
|
||||||
|
[int]$ExitCode,
|
||||||
|
[bool]$WaitForKeypress = $true
|
||||||
|
)
|
||||||
|
|
||||||
|
if ($ExitCode -ne 0) {
|
||||||
|
Write-Log "Script failed. Would you like to open the log file? (Y/N)" -Level 'PROMPT'
|
||||||
|
$openLog = Read-Host
|
||||||
|
if ($openLog -eq 'Y' -or $openLog -eq 'y') {
|
||||||
|
Start-Process notepad.exe -ArgumentList $LogFilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif ($WaitForKeypress) {
|
||||||
|
Write-Log "Press any key to exit..."
|
||||||
|
$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ExitCode
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-PythonExecutable {
|
||||||
|
param(
|
||||||
|
[string]$PythonExecutable
|
||||||
|
)
|
||||||
|
|
||||||
|
$DeactivateVenv = Join-Path $VenvDir "Scripts\Deactivate.bat"
|
||||||
|
if (Test-Path $DeactivateVenv) {
|
||||||
|
Write-Host "Deactivating virtual environment..." 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
& $DeactivateVenv
|
||||||
|
Write-Host "Virtual environment deactivated." 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "Deactivation script not found: $DeactivateVenv" 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
}
|
||||||
|
|
||||||
|
$PythonCmd = Get-Command $PythonExecutable -ErrorAction SilentlyContinue
|
||||||
|
if ($PythonCmd) {
|
||||||
|
$VersionOutput = & $PythonCmd.Source --version 2>&1
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
$Version = $VersionOutput | Select-String -Pattern "Python (\d+\.\d+\.\d+)" | ForEach-Object { $_.Matches.Groups[1].Value }
|
||||||
|
Write-Log "Python version $Version found using executable '$PythonExecutable'."
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Python executable '$PythonExecutable' not working correctly." -Level 'ERROR'
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Python executable '$PythonExecutable' not found." -Level 'ERROR'
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Find-PythonExecutable {
|
||||||
|
$PythonExecutables = @(
|
||||||
|
"python",
|
||||||
|
"python3.12",
|
||||||
|
"python3.11",
|
||||||
|
"python3.10",
|
||||||
|
"python3.9",
|
||||||
|
"python3",
|
||||||
|
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe",
|
||||||
|
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe",
|
||||||
|
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe",
|
||||||
|
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python39\python.exe",
|
||||||
|
"C:\Python312\python.exe",
|
||||||
|
"C:\Python311\python.exe",
|
||||||
|
"C:\Python310\python.exe",
|
||||||
|
"C:\Python39\python.exe"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
foreach ($Executable in $PythonExecutables) {
|
||||||
|
if (Test-PythonExecutable -PythonExecutable $Executable) {
|
||||||
|
return $Executable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
function Main {
|
||||||
|
"Starting the operations..." | Out-File $LogFilePath -Append
|
||||||
|
"Current directory: $(Get-Location)" | Out-File $LogFilePath -Append
|
||||||
|
|
||||||
|
# Exit on lower versions than Python 3.9 or when Python executable not found
|
||||||
|
$PythonExecutable = Find-PythonExecutable
|
||||||
|
if ($null -eq $PythonExecutable) {
|
||||||
|
Write-Log "No suitable Python executable found. Please ensure that Python 3.9 or higher is installed and available in the system PATH." -Level 'ERROR'
|
||||||
|
Exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Define the path to the Python executable in the virtual environment
|
||||||
|
$ActivateVenv = "$VenvDir\Scripts\Activate.ps1"
|
||||||
|
|
||||||
|
# Check if the virtual environment exists, if not, create it
|
||||||
|
if (-Not (Test-Path $ActivateVenv)) {
|
||||||
|
Write-Log "Virtual environment not found. Creating virtual environment..." -Level 'ERROR'
|
||||||
|
& $PythonExecutable -m venv $VenvName 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to create virtual environment." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Virtual environment created."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Activate the virtual environment and check if it was successful
|
||||||
|
Write-Log "Virtual environment found. Activating virtual environment..."
|
||||||
|
& $ActivateVenv 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
# Check if virtual environment is activated
|
||||||
|
if ($env:VIRTUAL_ENV) {
|
||||||
|
Write-Log "Virtual environment is activated at: $($env:VIRTUAL_ENV)"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Failed to activate virtual environment." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure pip
|
||||||
|
python -m ensurepip --default-pip 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
|
||||||
|
# Pull latest updates only if the repository state is not dirty
|
||||||
|
Write-Log "Checking if the repository is clean..."
|
||||||
|
$Status = & "git" status --porcelain
|
||||||
|
if ($Status) {
|
||||||
|
Write-Log "Changes in local git repository. Skipping git pull."
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Pulling latest updates..."
|
||||||
|
& "git" pull 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to pull updates from Git." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path "$VenvDir\Lib\site-packages\talib")) {
|
||||||
|
# Install TA-Lib using the virtual environment's pip
|
||||||
|
Write-Log "Installing TA-Lib using virtual environment's pip..."
|
||||||
|
python -m pip install --find-links=build_helpers\ --prefer-binary TA-Lib 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install TA-Lib." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Present options for requirement files
|
||||||
|
$SelectedIndices = Get-UserSelection -prompt "Select which requirement files to install:" -options $RequirementFiles -defaultChoice 'A'
|
||||||
|
|
||||||
|
# Cache the selected requirement files
|
||||||
|
$SelectedRequirementFiles = @()
|
||||||
|
$PipInstallArguments = @()
|
||||||
|
foreach ($Index in $SelectedIndices) {
|
||||||
|
$RelativePath = $RequirementFiles[$Index]
|
||||||
|
if (Test-Path $RelativePath) {
|
||||||
|
$SelectedRequirementFiles += $RelativePath
|
||||||
|
$PipInstallArguments += "-r", $RelativePath # Add each flag and path as separate elements
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "Requirement file not found: $RelativePath" -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($PipInstallArguments.Count -ne 0) {
|
||||||
|
& pip install @PipInstallArguments # Use array splatting to pass arguments correctly
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install freqtrade from setup using the virtual environment's Python
|
||||||
|
Write-Log "Installing freqtrade from setup..."
|
||||||
|
pip install -e . 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install freqtrade." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Installing freqUI..."
|
||||||
|
python freqtrade install-ui 2>&1 | Out-File $LogFilePath -Append
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Log "Failed to install freqUI." -Level 'ERROR'
|
||||||
|
Exit-Script -exitCode 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Installation/Update complete!"
|
||||||
|
Exit-Script -exitCode 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Call the Main function
|
||||||
|
Main
|
|
@ -50,6 +50,7 @@ def freqai_conf(default_conf, tmp_path):
|
||||||
freqaiconf.update(
|
freqaiconf.update(
|
||||||
{
|
{
|
||||||
"datadir": Path(default_conf["datadir"]),
|
"datadir": Path(default_conf["datadir"]),
|
||||||
|
"runmode": "backtest",
|
||||||
"strategy": "freqai_test_strat",
|
"strategy": "freqai_test_strat",
|
||||||
"user_data_dir": tmp_path,
|
"user_data_dir": tmp_path,
|
||||||
"strategy-path": "freqtrade/tests/strategy/strats",
|
"strategy-path": "freqtrade/tests/strategy/strats",
|
||||||
|
|
|
@ -773,6 +773,7 @@ def test_VolumePairList_whitelist_gen(
|
||||||
whitelist_result,
|
whitelist_result,
|
||||||
caplog,
|
caplog,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
whitelist_conf["runmode"] = "backtest"
|
||||||
whitelist_conf["pairlists"] = pairlists
|
whitelist_conf["pairlists"] = pairlists
|
||||||
whitelist_conf["stake_currency"] = base_currency
|
whitelist_conf["stake_currency"] = base_currency
|
||||||
|
|
||||||
|
@ -1270,6 +1271,7 @@ def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None:
|
||||||
{"method": "StaticPairList"},
|
{"method": "StaticPairList"},
|
||||||
{"method": "ShuffleFilter", "seed": 43},
|
{"method": "ShuffleFilter", "seed": 43},
|
||||||
]
|
]
|
||||||
|
whitelist_conf["runmode"] = "backtest"
|
||||||
|
|
||||||
exchange = get_patched_exchange(mocker, whitelist_conf)
|
exchange = get_patched_exchange(mocker, whitelist_conf)
|
||||||
plm = PairListManager(exchange, whitelist_conf)
|
plm = PairListManager(exchange, whitelist_conf)
|
||||||
|
|
177
tests/setup.Tests.ps1
Normal file
177
tests/setup.Tests.ps1
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
|
||||||
|
Describe "Setup and Tests" {
|
||||||
|
BeforeAll {
|
||||||
|
# Setup variables
|
||||||
|
$SetupScriptPath = Join-Path $PSScriptRoot "..\setup.ps1"
|
||||||
|
$Global:LogFilePath = Join-Path $env:TEMP "script_log.txt"
|
||||||
|
|
||||||
|
# Check if the setup script exists
|
||||||
|
if (-Not (Test-Path -Path $SetupScriptPath)) {
|
||||||
|
Write-Host "Error: setup.ps1 script not found at path: $SetupScriptPath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mock main to prevent it from running
|
||||||
|
Mock Main {}
|
||||||
|
|
||||||
|
. $SetupScriptPath
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Write-Log Tests" -Tag "Unit" {
|
||||||
|
It "should write INFO level log" {
|
||||||
|
if (Test-Path $Global:LogFilePath){
|
||||||
|
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log -Message "Test Info Message" -Level "INFO"
|
||||||
|
$Global:LogFilePath | Should -Exist
|
||||||
|
|
||||||
|
$LogContent = Get-Content $Global:LogFilePath
|
||||||
|
$LogContent | Should -Contain "INFO: Test Info Message"
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should write ERROR level log" {
|
||||||
|
if (Test-Path $Global:LogFilePath){
|
||||||
|
Remove-Item $Global:LogFilePath -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log -Message "Test Error Message" -Level "ERROR"
|
||||||
|
$Global:LogFilePath | Should -Exist
|
||||||
|
|
||||||
|
$LogContent = Get-Content $Global:LogFilePath
|
||||||
|
$LogContent | Should -Contain "ERROR: Test Error Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Get-UserSelection Tests" {
|
||||||
|
Context "Valid input" {
|
||||||
|
It "Should return the correct index for a valid single selection" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "B" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||||
|
$Result | Should -Be 1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return the correct index for a valid single selection" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "b" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||||
|
$Result | Should -Be 1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return the default choice when no input is provided" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options -defaultChoice "C"
|
||||||
|
$Result | Should -Be 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Invalid input" {
|
||||||
|
It "Should return -1 for an invalid letter selection" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "X" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||||
|
$Result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for a selection outside the valid range" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "D" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||||
|
$Result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for a non-letter input" {
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
Mock Read-Host { return "1" }
|
||||||
|
$Result = Get-UserSelection -prompt "Select an option" -options $Options
|
||||||
|
$Result | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return -1 for mixed valid and invalid input" {
|
||||||
|
Mock Read-Host { return "A,X,B,Y,C,Z" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||||
|
$Indices | Should -Be -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context "Multiple selections" {
|
||||||
|
It "Should handle valid input correctly" {
|
||||||
|
Mock Read-Host { return "A, B, C" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||||
|
$Indices | Should -Be @(0, 1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should handle valid input without whitespace correctly" {
|
||||||
|
Mock Read-Host { return "A,B,C" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||||
|
$Indices | Should -Be @(0, 1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return indices for selected options" {
|
||||||
|
Mock Read-Host { return "a,b" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options
|
||||||
|
$Indices | Should -Be @(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should return default choice if no input" {
|
||||||
|
Mock Read-Host { return "" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "C"
|
||||||
|
$Indices | Should -Be @(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should handle invalid input gracefully" {
|
||||||
|
Mock Read-Host { return "x,y,z" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options -defaultChoice "A"
|
||||||
|
$Indices | Should -Be -1
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should handle input without whitespace" {
|
||||||
|
Mock Read-Host { return "a,b,c" }
|
||||||
|
$Options = @("Option1", "Option2", "Option3")
|
||||||
|
$Indices = Get-UserSelection -prompt "Select options" -options $Options
|
||||||
|
$Indices | Should -Be @(0, 1, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Describe "Exit-Script Tests" -Tag "Unit" {
|
||||||
|
BeforeEach {
|
||||||
|
Mock Write-Log {}
|
||||||
|
Mock Start-Process {}
|
||||||
|
Mock Read-Host { return "Y" }
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should exit with the given exit code without waiting for key press" {
|
||||||
|
$ExitCode = Exit-Script -ExitCode 0 -isSubShell $true -waitForKeypress $false
|
||||||
|
$ExitCode | Should -Be 0
|
||||||
|
}
|
||||||
|
|
||||||
|
It "should prompt to open log file on error" {
|
||||||
|
Exit-Script -ExitCode 1 -isSubShell $true -waitForKeypress $false
|
||||||
|
Assert-MockCalled Read-Host -Exactly 1
|
||||||
|
Assert-MockCalled Start-Process -Exactly 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Context 'Find-PythonExecutable' {
|
||||||
|
It 'Returns the first valid Python executable' {
|
||||||
|
Mock Test-PythonExecutable { $true } -ParameterFilter { $PythonExecutable -eq 'python' }
|
||||||
|
$Result = Find-PythonExecutable
|
||||||
|
$Result | Should -Be 'python'
|
||||||
|
}
|
||||||
|
|
||||||
|
It 'Returns null if no valid Python executable is found' {
|
||||||
|
Mock Test-PythonExecutable { $false }
|
||||||
|
$Result = Find-PythonExecutable
|
||||||
|
$Result | Should -Be $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user