Files

301 lines
14 KiB
PowerShell
Raw Permalink Normal View History

# 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.*연결 실패"
}
}
}