301 lines
14 KiB
PowerShell
301 lines
14 KiB
PowerShell
|
|
# Tests\NetAppService.Tests.ps1
|
||
|
|
# Unit & Integration Tests for NetAppService Module
|
||
|
|
|
||
|
|
BeforeAll {
|
||
|
|
$scriptDir = Split-Path -Parent $PSScriptRoot
|
||
|
|
. (Join-Path $scriptDir "Modules\NetAppService.ps1")
|
||
|
|
}
|
||
|
|
|
||
|
|
Describe "NetAppService Unit Tests" -Tag "Mock", "Unit" {
|
||
|
|
Context "Resolve-NetAppParameters Precedence & Resolution" {
|
||
|
|
It "prefers explicit parameters over config defaults" {
|
||
|
|
$cfg = @{
|
||
|
|
NetApp = @{
|
||
|
|
ClusterEndpoint = "https://10.200.10.50"
|
||
|
|
Username = "config_user"
|
||
|
|
Password = "config_password"
|
||
|
|
DefaultSvm = "svm_config"
|
||
|
|
UseLive = $false
|
||
|
|
IgnoreSslErrors = $true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$resolved = Resolve-NetAppParameters -ClusterEndpoint "https://10.200.10.99" -Username "explicit_user" -SvmName "svm_explicit" -Config $cfg
|
||
|
|
$resolved.Endpoint | Should -Be "https://10.200.10.99"
|
||
|
|
$resolved.Username | Should -Be "explicit_user"
|
||
|
|
$resolved.SvmName | Should -Be "svm_explicit"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "falls back to config values when parameters are empty" {
|
||
|
|
$cfg = @{
|
||
|
|
NetApp = @{
|
||
|
|
ClusterEndpoint = "https://10.200.10.50"
|
||
|
|
Username = "config_admin"
|
||
|
|
DefaultSvm = "svm_prod01"
|
||
|
|
UseLive = $false
|
||
|
|
IgnoreSslErrors = $true
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$resolved = Resolve-NetAppParameters -Config $cfg
|
||
|
|
$resolved.Endpoint | Should -Be "https://10.200.10.50"
|
||
|
|
$resolved.Username | Should -Be "config_admin"
|
||
|
|
$resolved.SvmName | Should -Be "svm_prod01"
|
||
|
|
$resolved.UseLive | Should -BeFalse
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "Mock Data Retrieval (Qtrees, Quotas, Shares)" {
|
||
|
|
It "retrieves sample Qtrees with capacity and volume details" {
|
||
|
|
$qtrees = Get-NetAppSampleQtrees
|
||
|
|
$qtrees.Count | Should -BeGreaterThan 0
|
||
|
|
|
||
|
|
$first = $qtrees[0]
|
||
|
|
$first.QtreeName | Should -Not -BeNullOrEmpty
|
||
|
|
$first.Volume | Should -Not -BeNullOrEmpty
|
||
|
|
$first.SecurityStyle | Should -Not -BeNullOrEmpty
|
||
|
|
$first.IsLive | Should -BeFalse
|
||
|
|
}
|
||
|
|
|
||
|
|
It "filters sample Qtrees by Volume name" {
|
||
|
|
$filtered = Get-NetAppSampleQtrees -Filter "vol_cifs_share01"
|
||
|
|
$filtered.Count | Should -Be 2
|
||
|
|
foreach ($q in $filtered) {
|
||
|
|
$q.Volume | Should -Be "vol_cifs_share01"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
It "retrieves sample Quota rules with Soft and Hard limits" {
|
||
|
|
$quotas = Get-NetAppSampleQuotas
|
||
|
|
$quotas.Count | Should -BeGreaterThan 0
|
||
|
|
|
||
|
|
foreach ($q in $quotas) {
|
||
|
|
$q.VolumeQtree | Should -Not -BeNullOrEmpty
|
||
|
|
$q.QuotaType | Should -Not -BeNullOrEmpty
|
||
|
|
$q.Target | Should -Not -BeNullOrEmpty
|
||
|
|
$q.HardLimit | Should -Not -BeNullOrEmpty
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
It "retrieves sample CIFS shares with paths and permissions" {
|
||
|
|
$shares = Get-NetAppSampleShares
|
||
|
|
$shares.Count | Should -BeGreaterThan 0
|
||
|
|
|
||
|
|
foreach ($s in $shares) {
|
||
|
|
$s.ShareName | Should -Not -BeNullOrEmpty
|
||
|
|
$s.Path | Should -Not -BeNullOrEmpty
|
||
|
|
$s.Comment | Should -Not -BeNullOrEmpty
|
||
|
|
$s.AclSummary | Should -Not -BeNullOrEmpty
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "Storage Action Operations (Demo Simulation Mode)" {
|
||
|
|
It "simulates Qtree creation on specified volume" {
|
||
|
|
$cfg = @{ NetApp = @{ UseLive = $false } }
|
||
|
|
$res = New-NetAppQtreeAction -Volume "vol_cifs_share01" -QtreeName "qt_new_project" -SecurityStyle "ntfs" -Config $cfg
|
||
|
|
$res.Success | Should -BeTrue
|
||
|
|
$res.Message | Should -Match "qt_new_project"
|
||
|
|
$res.Message | Should -Match "데모 모드"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "simulates CIFS share creation with directory path" {
|
||
|
|
$cfg = @{ NetApp = @{ UseLive = $false } }
|
||
|
|
$res = New-NetAppShareAction -ShareName "Marketing_Share" -Path "/vol_cifs_share01/qt_marketing" -Comment "Marketing folder" -Config $cfg
|
||
|
|
$res.Success | Should -BeTrue
|
||
|
|
$res.Message | Should -Match "Marketing_Share"
|
||
|
|
$res.Message | Should -Match "가상 생성"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "simulates Quota policy adjustment with Soft and Hard limits" {
|
||
|
|
$cfg = @{ NetApp = @{ UseLive = $false } }
|
||
|
|
$res = Set-NetAppQuotaRule -Volume "vol_cifs_share01" -Qtree "qt_marketing" -SoftLimitGb 200 -HardLimitGb 250 -Config $cfg
|
||
|
|
$res.Success | Should -BeTrue
|
||
|
|
$res.Message | Should -Match "200GB"
|
||
|
|
$res.Message | Should -Match "250GB"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "Active DC Discovery: Edge Case Parsing (REST JSON Schema Variants)" {
|
||
|
|
It "parses discovered_servers schema using 'server_name' property" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
server_name = "dc04.corp.local"
|
||
|
|
server_ip = "10.20.1.14"
|
||
|
|
server_type = "dc"
|
||
|
|
preference = "preferred"
|
||
|
|
state = "ok"
|
||
|
|
node = [PSCustomObject]@{ name = "node-01" }
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc04.corp.local"
|
||
|
|
$res.Node | Should -Be "node-01"
|
||
|
|
$res.IsLive | Should -BeTrue
|
||
|
|
}
|
||
|
|
|
||
|
|
It "falls back to 'name' property when server_name is absent" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
name = "dc02.corp.local"
|
||
|
|
server_ip = "10.10.1.12"
|
||
|
|
server_type = "dc"
|
||
|
|
preference = "favored"
|
||
|
|
state = "ok"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc02.corp.local"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "falls back to 'server' property when both server_name and name are absent" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
server = "dc03.corp.local"
|
||
|
|
server_ip = "10.10.1.13"
|
||
|
|
server_type = "dc"
|
||
|
|
preference = "regular"
|
||
|
|
state = "ok"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc03.corp.local"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "falls back to 'server_ip' when no hostname is provided" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
server_ip = "10.30.1.15"
|
||
|
|
server_type = "dc"
|
||
|
|
preference = "regular"
|
||
|
|
state = "ok"
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "10.30.1.15"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "Active DC Discovery: Preference Sorting & State Filtering" {
|
||
|
|
It "selects 'preferred' over 'favored' and 'regular' among OK servers" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{ server_name = "dc-regular.corp.local"; state = "ok"; server_type = "dc"; preference = "regular" },
|
||
|
|
[PSCustomObject]@{ server_name = "dc-preferred.corp.local"; state = "ok"; server_type = "dc"; preference = "preferred" },
|
||
|
|
[PSCustomObject]@{ server_name = "dc-favored.corp.local"; state = "ok"; server_type = "dc"; preference = "favored" }
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc-preferred.corp.local"
|
||
|
|
$res.Preference | Should -Be "preferred"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "discards down servers even if their preference is preferred" {
|
||
|
|
$mockRecord = [PSCustomObject]@{
|
||
|
|
records = @(
|
||
|
|
[PSCustomObject]@{
|
||
|
|
svm = [PSCustomObject]@{ name = "svm_cifs_prod01" }
|
||
|
|
discovered_servers = @(
|
||
|
|
[PSCustomObject]@{ server_name = "dc-down.corp.local"; state = "down"; server_type = "dc"; preference = "preferred" },
|
||
|
|
[PSCustomObject]@{ server_name = "dc-active.corp.local"; state = "ok"; server_type = "dc"; preference = "favored" }
|
||
|
|
)
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
Mock Invoke-NetAppRestRequest { return $mockRecord }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://fake-cluster" -SvmName "svm_cifs_prod01" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc-active.corp.local"
|
||
|
|
$res.Preference | Should -Be "favored"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "returns demo active DC dc04.corp.local when UseLive is false" {
|
||
|
|
$cfg = @{ NetApp = @{ UseLive = $false } }
|
||
|
|
$activeDc = Get-NetAppActiveDc -Config $cfg
|
||
|
|
$activeDc.ActiveDC | Should -Be "dc04.corp.local"
|
||
|
|
$activeDc.Node | Should -Be "ontap-node-02"
|
||
|
|
$activeDc.Status | Should -Match "OK"
|
||
|
|
$activeDc.IsLive | Should -BeFalse
|
||
|
|
$activeDc.AllOkDcs | Should -Contain "dc04.corp.local"
|
||
|
|
$activeDc.AllOkDcs | Should -Contain "dc01.corp.local"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "falls back to demo DC when live REST endpoints fail" {
|
||
|
|
Mock Invoke-NetAppRestRequest { throw "Network unreachable" }
|
||
|
|
$res = Get-NetAppActiveDc -ClusterEndpoint "https://bad-cluster" -UseLive $true
|
||
|
|
$res.ActiveDC | Should -Be "dc04.corp.local"
|
||
|
|
$res.IsLive | Should -BeFalse
|
||
|
|
$res.FallbackReason | Should -Match "Network unreachable"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "CIFS Share ACL Binding with Smart Retry Corridor" {
|
||
|
|
It "successfully applies share ACL in demo mode" {
|
||
|
|
$cfg = @{ NetApp = @{ UseLive = $false } }
|
||
|
|
$res = Set-NetAppShareAcl -ShareName "Finance_Share" -UserOrGroup "Finance_Share" -Permission "change" -ActionType "grant" -Config $cfg
|
||
|
|
$res.Success | Should -BeTrue
|
||
|
|
$res.Message | Should -Match "Finance_Share"
|
||
|
|
$res.Message | Should -Match "데모 모드"
|
||
|
|
}
|
||
|
|
|
||
|
|
It "formats retry count correctly without Unicode variable expansion failure" {
|
||
|
|
# Regression test for Korean interpolation bug where $MaxRetries회 evaluated to empty
|
||
|
|
$attempt = 3
|
||
|
|
$maxRetries = 3
|
||
|
|
$lastError = "SID lookup timeout"
|
||
|
|
$obj = [PSCustomObject]@{
|
||
|
|
Success = $false
|
||
|
|
Message = "NetApp Share ACL 반영 실패 ($($maxRetries)회 재시도 초과): $lastError"
|
||
|
|
Attempts = $attempt
|
||
|
|
}
|
||
|
|
$obj.Message | Should -Match "\(3회 재시도 초과\)"
|
||
|
|
$obj.Message | Should -Not -Match "\(\s*회 재시도 초과\)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Context "Live Infrastructure Path & Timeout Handling" -Tag "Live" {
|
||
|
|
It "gracefully fails over and reports unreachable endpoint in live mode" {
|
||
|
|
$res = Test-NetAppConnection -ClusterEndpoint "https://127.0.0.1:65531" -TimeoutSec 1
|
||
|
|
$res.Success | Should -BeFalse
|
||
|
|
$res.Message | Should -Match "NetApp.*연결 실패"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|