diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3ec074f8..d7d3a32f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -106,6 +106,21 @@ switch ($PSCmdlet.ParameterSetName) { $result = Get-SdnResource @ncRestParams -ResourceRef $resourceRef ``` +## Pester Testing + +All tests are offline (mock-based) in `tests/offline/`. See `.github/instructions/pester-tests.instructions.md` for the full authoring guide. + +**When adding a new function, always add corresponding Pester tests:** +- Pure utility functions (Format-*, Confirm-*, Convert-*) → Pattern A (InModuleScope, no mocking) +- Functions calling NC REST API → Pattern B (mock `Invoke-RestMethodWithRetry` inside `InModuleScope SdnDiag.NetworkController`) +- Functions calling remote commands → Pattern C (mock `Invoke-PSRemoteCommand` inside the nested module scope) + +**Test file convention:** Named after the source module (e.g., `SdnDiag.Utilities.psm1` → `Utilities.Tests.ps1`) + +**Mock data:** Uses `DVLAB` prefix naming. JSON files in `tests/offline/data/SdnApiResources/` auto-load into `$Global:PesterOfflineTests.SdnApiResources`. + +**Running:** Build the module first (`.\build.ps1`), then `.\tests\offline\RunTests.ps1` + ## Security Best Practices - Never log credentials or secrets - Use SecureString for password parameters diff --git a/.github/instructions/pester-review.instructions.md b/.github/instructions/pester-review.instructions.md new file mode 100644 index 00000000..f6553dbe --- /dev/null +++ b/.github/instructions/pester-review.instructions.md @@ -0,0 +1,35 @@ +--- +description: Review for missing Pester test coverage on new or modified functions +applyTo: "src/**/*.psm1,src/**/*.ps1" +--- + +# Code Review: Pester Test Coverage + +When reviewing changes to PowerShell source files in `src/`, check that corresponding Pester tests exist. + +## Rules + +1. **New exported functions MUST have tests.** If a new function is added to any `.psm1` file, there should be a corresponding test in `tests/offline/.Tests.ps1`. Flag if missing. + +2. **Modified function signatures should update tests.** If parameters are added, renamed, or removed, existing tests should reflect the change. + +3. **Bug fixes should add a regression test.** If the PR fixes a bug, there should be a test that would have caught it. + +## What to check + +- Look for new `function -Sdn` definitions in the diff +- **Verify the function is exported:** Check `src/SdnDiagnostics.psd1` `FunctionsToExport` — only exported functions require tests +- Verify a `Describe ' - '` block exists in the corresponding test file +- If no test file exists for the module yet, flag that one should be created + +## How to flag + +If tests are missing, comment: + +> This PR adds/modifies `` but no corresponding Pester test was found in `tests/offline/`. Please add offline tests following the patterns in `tests/CONTRIBUTING_TESTS.md`. + +## Exceptions (do not flag) + +- Private helper functions (not exported, names without `Sdn` prefix) +- Changes to build scripts, manifests, or documentation only +- Trivial parameter alias additions diff --git a/.github/instructions/pester-tests.instructions.md b/.github/instructions/pester-tests.instructions.md new file mode 100644 index 00000000..a92194af --- /dev/null +++ b/.github/instructions/pester-tests.instructions.md @@ -0,0 +1,203 @@ +--- +description: Pester test authoring patterns and conventions for SdnDiagnostics +applyTo: "tests/**/*.ps1,tests/**/*.md" +--- + +# Pester Test Authoring Instructions + +Apply when creating, modifying, or expanding Pester tests for SdnDiagnostics. + +## Test Location and Structure + +All tests are **offline** (mock-based). No live SDN environment is required. + +- Test files: `tests/offline/.Tests.ps1` +- Mock data: `tests/offline/data/SdnApiResources/*.json` +- Runner: `tests/offline/RunTests.ps1` +- The module must be built first (`.\build.ps1`) before tests can run + +## File Naming + +| Source Module | Test File | +|---------------|-----------| +| `SdnDiag.Utilities.psm1` | `Utilities.Tests.ps1` | +| `SdnDiag.NetworkController.psm1` | `NetworkController.Tests.ps1` | +| `SdnDiag.LoadBalancerMux.psm1` | `SoftwareLoadBalancer.Tests.ps1` | +| `SdnDiag.Health.psm1` | `Health.Tests.ps1` | +| `SdnDiag.Server.psm1` | `Server.Tests.ps1` | +| `SdnDiag.Gateway.psm1` | `Gateway.Tests.ps1` | + +## Mock Data Access + +`RunTests.ps1` loads all JSON files into globals before tests execute: + +```powershell +# Collection access (returns array of objects) +$Global:PesterOfflineTests.SdnApiResources['servers'] +$Global:PesterOfflineTests.SdnApiResources['networkInterfaces'] + +# Single-resource lookup by resourceRef path +$Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N01'] +``` + +## Module Scope and Mocking + +SdnDiagnostics uses nested modules (SdnDiag.Utilities, SdnDiag.NetworkController, etc.). Each nested module has its own session state. This means: + +- **Private/internal functions** (not exported) must be called inside `InModuleScope SdnDiagnostics { ... }` +- **Mocks for functions called within nested modules** must use `InModuleScope ` to intercept internal calls +- `Mock -ModuleName SdnDiagnostics` does NOT intercept calls between functions within nested modules + +## Three Mock Patterns + +### Pattern A: Pure Unit Test (private utility functions) + +Use for internal functions that transform input without external calls (Format-*, Confirm-*, Convert-*). These are NOT exported, so `InModuleScope` is required: + +```powershell +Describe 'Format-MyFunction' { + It "Transforms input correctly" { + InModuleScope SdnDiagnostics { + $result = Format-MyFunction -Input "test" + $result | Should -Be "expected" + } + } + + It "Handles null gracefully" { + InModuleScope SdnDiagnostics { + { Format-MyFunction -Input $null } | Should -Throw + } + } +} +``` + +### Pattern B: Mock Invoke-RestMethodWithRetry (NC REST functions) + +Use for any function that internally queries the Network Controller REST API. Mock `Invoke-RestMethodWithRetry` inside `InModuleScope SdnDiag.NetworkController` to intercept the HTTP call: + +```powershell +Describe 'Get-SdnMyResource' { + It "Returns resources from NC" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $result = Get-SdnMyResource -NcUri "https://dvlab-nc.dvlab.contoso.local" + $result | Should -Not -BeNullOrEmpty + } + } +} +``` + +**Key points:** +- The mock must be inside `InModuleScope SdnDiag.NetworkController` (where the functions live) +- Always use `-NcUri` as a named parameter (it is NOT positional) +- The mock handles both collection requests (returns `{value: [...]}`) and single-resource lookups (returns the object directly via `SdnApiResourcesByRef`) + +### Pattern C: Mock Remote Commands + +Use for functions that execute commands on remote hosts via `Invoke-PSRemoteCommand`: + +```powershell +Describe 'Get-SdnMyRemoteData' { + It "Processes remote output" { + InModuleScope SdnDiag.Server { + Mock Invoke-PSRemoteCommand { + return @{ Status = "OK"; Data = "mocked-response" } + } + $result = Get-SdnMyRemoteData -ComputerName "DVLAB-S1-N01" + $result.Status | Should -Be "OK" + } + } +} +``` + +## Mock Data Naming Conventions + +All mock data uses a consistent fictional deployment: + +| Element | Convention | +|---------|-----------| +| Deployment prefix | `DVLAB` | +| Domain | `dvlab.contoso.local` | +| Hyper-V servers | `DVLAB-S1-N01` through `DVLAB-S1-N04` | +| Network Controllers | `DVLAB-NC01` through `DVLAB-NC03` | +| NC URI | `https://dvlab-nc.dvlab.contoso.local` | +| Gateways | `DVLAB-GW01` through `DVLAB-GW03` | +| Muxes | `DVLAB-MUX01`, `DVLAB-MUX02` | + +**Rules:** +- Never use real customer data or deployment names +- Keep naming consistent across all JSON files (same server name everywhere) +- IP addresses may use any RFC1918 range without randomization +- DVLAB-S1-N04 is intentionally in `Failed` state for health-detection tests + +## Adding Mock Data + +JSON files use the NC REST API response wrapper format: + +```json +{ + "value": [ + { + "resourceRef": "/resourceType/resource-id-0001", + "resourceId": "resource-id-0001", + "properties": { + "provisioningState": "Succeeded" + } + } + ], + "nextLink": "" +} +``` + +**Exception:** Singleton configuration resources (e.g., `iDNSServer_configuration.json`, `loadBalancerManager_config.json`) use the raw object without the `value` wrapper. `RunTests.ps1` handles both formats automatically. + +The filename (minus `.json`) becomes the lookup key in `$Global:PesterOfflineTests.SdnApiResources`. + +## Test Design Rules + +1. **One behavior per `It` block** — test one logical behavior; multiple related assertions on the same result are fine +2. **Test both success and failure paths** — include resources with Failed state +3. **Descriptive test names** — describe WHAT is validated ("Returns 4 servers"), not HOW +4. **Independent Describe blocks** — no cross-block state dependencies +5. **Mock + call inside the same `InModuleScope` block** — never separate them; `BeforeAll` mocks do not cross `InModuleScope` boundaries +6. **Use Pester v5+ syntax** — `Should -Be`, not legacy `Should Be` +7. **Tag tests** when grouping: `Describe 'My Test' -Tag 'Unit' { ... }` + +## Running Tests + +```powershell +# Build the module first +.\build.ps1 + +# Run all offline tests +cd tests\offline +.\RunTests.ps1 + +# Run a specific file +.\RunTests.ps1 -TestFile ".\Utilities.Tests.ps1" + +# Run by tag +.\RunTests.ps1 -Tag "Unit" +``` + +## CI Pipeline + +Tests run automatically via `.github/workflows/pester-tests.yml` on every PR and push to main. A failing test blocks the PR. + +## Key Test Scenarios in Mock Data + +- **Healthy resources:** DVLAB-S1-N01 through N03 (provisioningState: Succeeded) +- **Unhealthy resource:** DVLAB-S1-N04 (provisioningState: Failed, configurationState: Failure) +- **Outbound NAT chain:** tenantvm2 → lb-outbound-0001/OutboundNatPool → pip-outbound-0001 (40.40.40.4) +- **Direct VIP:** tenantvm1 → publicIPAddress → pip-tenant-0001 (40.40.40.5) +- **MAC pools:** Pool with range 00-11-22-00-00-00 to 00-11-22-FF-FF-FF diff --git a/.github/prompts/add-sdn-function.prompt.md b/.github/prompts/add-sdn-function.prompt.md new file mode 100644 index 00000000..491ee7cc --- /dev/null +++ b/.github/prompts/add-sdn-function.prompt.md @@ -0,0 +1,201 @@ +--- +description: Add a new function to SdnDiagnostics with corresponding Pester tests +mode: agent +tools: + - view + - edit + - create + - grep + - glob + - powershell +--- + +# Add SDN Function + +Create a new function in the SdnDiagnostics module and generate its corresponding offline Pester tests. + +## Phase 1: Create the Function + +### Determine the target module + +Place the function in the correct module based on its role: + +| Module | Purpose | Location | +|--------|---------|----------| +| `SdnDiag.Utilities` | Pure helpers (Format-*, Confirm-*, Convert-*) | `src/modules/SdnDiag.Utilities.psm1` | +| `SdnDiag.Common` | Shared SDN operations | `src/modules/SdnDiag.Common.psm1` | +| `SdnDiag.NetworkController` | NC REST API operations | `src/modules/SdnDiag.NetworkController.psm1` | +| `SdnDiag.Server` | Hyper-V host operations | `src/modules/SdnDiag.Server.psm1` | +| `SdnDiag.Gateway` | Gateway operations | `src/modules/SdnDiag.Gateway.psm1` | +| `SdnDiag.LoadBalancerMux` | Mux operations | `src/modules/SdnDiag.LoadBalancerMux.psm1` | +| `SdnDiag.Health` | Health validation logic | `src/modules/SdnDiag.Health.psm1` | + +### Function structure requirements + +```powershell +function Verb-SdnNoun { + <# + .SYNOPSIS + Brief description. + .DESCRIPTION + Detailed description. + .PARAMETER ParameterName + Parameter description. + .EXAMPLE + PS> Verb-SdnNoun -Parameter "value" + #> + + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$RequiredParam, + + [Parameter(Mandatory = $false)] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $Credential = [System.Management.Automation.PSCredential]::Empty + ) + + try { + # Function logic + } + catch { + $_ | Trace-Exception + $_ | Write-Error + } +} +``` + +### For NC REST functions, include parameter sets + +```powershell +[CmdletBinding(DefaultParameterSetName = 'RestCredential')] +param( + [Parameter(Mandatory = $true)] + [uri]$NcUri, + + [Parameter(Mandatory = $true, ParameterSetName = 'RestCertificate')] + [X509Certificate]$NcRestCertificate, + + [Parameter(Mandatory = $false, ParameterSetName = 'RestCredential')] + [System.Management.Automation.PSCredential] + [System.Management.Automation.Credential()] + $NcRestCredential = [System.Management.Automation.PSCredential]::Empty +) +``` + +### Conventions + +- Use approved PowerShell verbs (Get-, Set-, New-, Remove-, Test-, Confirm-, Format-) +- PascalCase for function/parameter names, camelCase for local variables +- Use `Trace-Output` for logging (not Write-Host/Write-Verbose) +- Use `Invoke-PSRemoteCommand` for remote execution +- Use `Get-SdnResource` for NC REST API calls + +## Phase 2: Create Pester Tests + +After creating the function, generate its offline Pester tests. + +### Determine the mock pattern + +1. **Read the function source** — identify what external calls it makes +2. **Select pattern:** + - **Pattern A (pure unit):** No external calls — test inputs/outputs directly. Wrap in `InModuleScope SdnDiagnostics { ... }` for private/internal functions. + - **Pattern B (NC REST):** Calls `Invoke-RestMethodWithRetry` internally — mock it inside `InModuleScope SdnDiag.NetworkController` + - **Pattern C (remote command):** Calls `Invoke-PSRemoteCommand` — mock it inside `InModuleScope ` + +### Create or update the test file + +Test file: `tests/offline/.Tests.ps1` (e.g., `NetworkController.Tests.ps1`) + +#### Pattern A — Pure unit test (private functions) + +```powershell +Describe 'Utilities - Format-MyFunction' { + It "Returns expected output for valid input" { + InModuleScope SdnDiagnostics { + $result = Format-MyFunction -Input "test" + $result | Should -Be "expected" + } + } + + It "Handles edge case" { + InModuleScope SdnDiagnostics { + { Format-MyFunction -Input $null } | Should -Throw + } + } +} +``` + +#### Pattern B — NC REST mock + +```powershell +Describe 'NetworkController - Get-SdnMyResource' { + It "Returns resources" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $result = Get-SdnMyResource -NcUri "https://dvlab-nc.dvlab.contoso.local" + $result | Should -Not -BeNullOrEmpty + } + } +} +``` + +#### Pattern C — Remote command mock + +```powershell +Describe 'Server - Get-SdnMyRemoteData' { + It "Returns remote data" { + InModuleScope SdnDiag.Server { + Mock Invoke-PSRemoteCommand { + return @{ Status = "OK"; Data = "mocked" } + } + $result = Get-SdnMyRemoteData -ComputerName "DVLAB-S1-N01" + $result.Status | Should -Be "OK" + } + } +} +``` + +### Mock data conventions + +- Prefix: `DVLAB`, Domain: `dvlab.contoso.local` +- NC URI: `https://dvlab-nc.dvlab.contoso.local` +- Servers: `DVLAB-S1-N01` through `DVLAB-S1-N04` (N04 is intentionally Failed) +- NCs: `DVLAB-NC01` through `DVLAB-NC03` +- Gateways: `DVLAB-GW01` through `DVLAB-GW03` +- Muxes: `DVLAB-MUX01`, `DVLAB-MUX02` + +If new mock data is needed, add to `tests/offline/data/SdnApiResources/` using `{ "value": [...], "nextLink": "" }` wrapper format for collection resources. Singleton configuration endpoints (e.g., iDNS, load balancer manager) should use the raw object without the `value` wrapper. + +### Test rules + +- Pester v5+ syntax only (`Should -Be`, not `Should Be`) +- One behavior per `It` block — multiple related assertions on the same result are allowed +- Test both success and failure paths +- Use `InModuleScope SdnDiagnostics { ... }` for private/internal utility functions +- Use `InModuleScope SdnDiag.NetworkController { ... }` for NC REST mocks (mock + call together) +- Always use `-NcUri` as a named parameter (it is NOT positional) +- Descriptive names: describe WHAT is validated +- Independent `Describe` blocks — no cross-block dependencies + +## Checklist + +After both phases complete, verify: + +- [ ] Function uses approved verb and follows `Verb-SdnNoun` naming +- [ ] Function includes comment-based help (Synopsis, Description, Parameters, Example) +- [ ] Function includes try/catch with `Trace-Exception` and `Write-Error` +- [ ] Test file exists in `tests/offline/` with at least 2 test cases (happy path + edge case) +- [ ] Mock data references match existing files in `tests/offline/data/SdnApiResources/` +- [ ] Any new mock data uses DVLAB naming and the `{value:[]}` wrapper format diff --git a/.github/workflows/build-pipeline.yml b/.github/workflows/build-pipeline.yml index 710647aa..536bd324 100644 --- a/.github/workflows/build-pipeline.yml +++ b/.github/workflows/build-pipeline.yml @@ -41,6 +41,12 @@ jobs: & .\build.ps1 shell: powershell + - name: 'Run Offline Pester Tests' + run: | + Set-Location -Path .\main\tests\offline + .\RunTests.ps1 + shell: powershell + - name: 'Publish to Nuget Gallery' run: | nuget.exe push ".\main\out\packages\SdnDiagnostics.*.nupkg" -ApiKey ${{ secrets.NUGET_AUTH_TOKEN }} -Source https://api.nuget.org/v3/index.json diff --git a/.github/workflows/pester-tests.yml b/.github/workflows/pester-tests.yml new file mode 100644 index 00000000..45015024 --- /dev/null +++ b/.github/workflows/pester-tests.yml @@ -0,0 +1,37 @@ +name: Pester Tests + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + + workflow_dispatch: + +permissions: + contents: read + +jobs: + test: + name: Offline Pester Tests + runs-on: windows-latest + + steps: + - name: Harden Runner + uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + + - name: Build Module + run: | + & .\build.ps1 + shell: powershell + + - name: Run Offline Pester Tests + run: | + Set-Location -Path .\tests\offline + .\RunTests.ps1 + shell: powershell diff --git a/.github/workflows/server2019-sdntest-pr.yml b/.github/workflows/server2019-sdntest-pr.yml deleted file mode 100644 index 96121bc2..00000000 --- a/.github/workflows/server2019-sdntest-pr.yml +++ /dev/null @@ -1,55 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Server2019SDNPullRequest - -# Controls when the workflow will run -on: - # Triggers the workflow on pull request events but only for the main branch - pull_request: - branches: - - main - paths: - - 'src/**' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -permissions: - contents: read - -jobs: - # This workflow contains a single job called "build" - build-and-test: - # The type of runner that the job will run on - runs-on: [self-hosted,Windows,X64] - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Harden Runner - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 - with: - egress-policy: audit - - - name: Cleanup existing files - run: | - Remove-Item -Path .\* -Recurse -Force -Verbose - shell: powershell - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout SdnDiagnostics repo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - - # Runs a single command using the runners shell - - name: Build SdnDiagnostics Module and Nuget Package - run: | - nuget.exe update -self - .\build.ps1 - shell: powershell - - # Run the test configuration file that is locally on the test environment - - name: Run online validation tests - run: .\tests\online\RunTests.ps1 -ConfigurationFile "..\SdnDiagnosticsTestConfig.psd1" - shell: powershell diff --git a/.github/workflows/server2019-sdntest.yml b/.github/workflows/server2019-sdntest.yml deleted file mode 100644 index d59eb1f7..00000000 --- a/.github/workflows/server2019-sdntest.yml +++ /dev/null @@ -1,53 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Server2019SDN - -# Controls when the workflow will run -on: - # Triggers the workflow on push request events but only for the main branch - push: - branches: - - main - paths: - - 'src/**' - - # Allows you to run this workflow manually from the Actions tab - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -permissions: - contents: read - -jobs: - # This workflow contains a single job called "build" - build-and-test: - # The type of runner that the job will run on - runs-on: [self-hosted,Windows,X64] - - # Steps represent a sequence of tasks that will be executed as part of the job - steps: - - name: Harden Runner - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 - with: - egress-policy: audit - - - name: Cleanup existing files - run: | - Remove-Item -Path .\* -Recurse -Force -Verbose - shell: powershell - - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - name: Checkout SdnDiagnostics repo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: main - - # Runs a single command using the runners shell - - name: Build SdnDiagnostics module - run: .\build.ps1 - shell: powershell - - # Run the test configuration file that is locally on the test environment - - name: Run online validation tests - run: .\tests\online\RunTests.ps1 -ConfigurationFile "..\SdnDiagnosticsTestConfig.psd1" - shell: powershell diff --git a/src/modules/SdnDiag.Utilities.psm1 b/src/modules/SdnDiag.Utilities.psm1 index 2746ccdc..ccd93c17 100644 --- a/src/modules/SdnDiag.Utilities.psm1 +++ b/src/modules/SdnDiag.Utilities.psm1 @@ -232,7 +232,13 @@ function Confirm-IpAddressInCidrRange { $network = [System.BitConverter]::ToUInt32($network, 0) # Calculate the subnet mask from the prefix length - $mask = [uint32]::MaxValue -shl (32 - $prefixLength) + # Special-case /0: 32-bit shift wraps modulo 32, so handle it explicitly + if ($prefixLength -eq 0) { + $mask = [uint32]0 + } + else { + $mask = [uint32]::MaxValue -shl (32 - $prefixLength) + } # Calculate the network address and broadcast address $networkAddress = $network -band $mask diff --git a/tests/CONTRIBUTING_TESTS.md b/tests/CONTRIBUTING_TESTS.md new file mode 100644 index 00000000..4245f7a0 --- /dev/null +++ b/tests/CONTRIBUTING_TESTS.md @@ -0,0 +1,247 @@ +# Contributing Pester Tests + +This guide explains how to add new Pester tests to the SdnDiagnostics project. + +## Test Categories + +| Category | Location | When to Use | +|----------|----------|-------------| +| **Offline** | `tests/offline/` | Function can be tested with mocked data, no live SDN deployment needed | + +**All tests should be offline.** If a function's behavior can be validated through mocking, write an offline test. + +## Adding a New Offline Test + +### Step 1: Choose or Create a Test File + +Test files are named after the module they test: + +| Module | Test File | +|--------|-----------| +| `SdnDiag.Utilities.psm1` | `Utilities.Tests.ps1` | +| `SdnDiag.NetworkController.psm1` | `NetworkController.Tests.ps1` | +| `SdnDiag.LoadBalancerMux.psm1` | `SoftwareLoadBalancer.Tests.ps1` | +| `SdnDiag.Health.psm1` | `Health.Tests.ps1` | +| `SdnDiag.Server.psm1` | `Server.Tests.ps1` | +| `SdnDiag.Gateway.psm1` | `Gateway.Tests.ps1` | + +If your function belongs to a module without a test file, create one following the naming pattern `.Tests.ps1`. + +### Step 2: Understand the Mock Data Structure + +Mock data lives in `tests/offline/data/SdnApiResources/`. The `RunTests.ps1` script loads all JSON files into a global hashtable: + +```powershell +$Global:PesterOfflineTests.SdnApiResources['servers'] # Array of server objects +$Global:PesterOfflineTests.SdnApiResources['gateways'] # Array of gateway objects +$Global:PesterOfflineTests.SdnApiResources['networkInterfaces'] # Array of NIC objects +# etc. + +$Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N01'] # Lookup by resourceRef +``` + +**JSON file format:** Each file wraps data in `{ "value": [...], "nextLink": "" }` matching the NC REST API response format. Singleton configuration resources (e.g., iDNS) may use a raw object without the `value` wrapper — `RunTests.ps1` handles both formats. + +### Step 3: Understand the Module Architecture + +SdnDiagnostics uses **nested modules** (`NestedModules` in the manifest). Each nested module has its own session state. This is critical for mocking: + +- **Private/internal functions** (e.g., `Format-*`, `Confirm-*`) are NOT exported — you must use `InModuleScope SdnDiagnostics { ... }` to access them +- **Functions in nested modules** (e.g., `Get-SdnServer` in `SdnDiag.NetworkController`) execute in their nested module's scope — mocks must be placed in THAT scope +- **Cross-module calls** (e.g., Health → NetworkController) require mocking the called function in the caller's module scope + +### Step 4: Write Your Test + +#### Pattern A: Pure Unit Tests (private/internal functions) + +For private functions that are not exported (e.g., `Format-MacAddressWithDashes`, `Confirm-IsAdmin`): + +```powershell +Describe 'Utilities - Format-MyFunction' { + It "Returns expected output for valid input" { + InModuleScope SdnDiagnostics { + $result = Format-MyFunction -Input "test" + $result | Should -Be "expected" + } + } + + It "Throws on invalid input" { + InModuleScope SdnDiagnostics { + { Format-MyFunction -Input $null } | Should -Throw + } + } +} +``` + +#### Pattern B: NC REST functions (mock Invoke-RestMethodWithRetry) + +For functions in `SdnDiag.NetworkController` that call `Invoke-RestMethodWithRetry`: + +```powershell +Describe 'NetworkController - Get-SdnMyResource' { + It "Returns resources" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $result = Get-SdnMyResource -NcUri "https://dvlab-nc.dvlab.contoso.local" + $result | Should -Not -BeNullOrEmpty + } + } +} +``` + +**Why this pattern?** `Get-SdnServer` → `Get-SdnResource` → `Invoke-RestMethodWithRetry` all execute within `SdnDiag.NetworkController`'s session state. The mock must be injected into THAT scope. Mocking at the parent module level (`-ModuleName SdnDiagnostics`) does NOT intercept calls between functions within nested modules. + +#### Pattern C: Health functions (invoke via InModuleScope with mocked Get-SdnResource) + +Health functions (`Test-SdnResourceProvisioningState`, `Test-SdnResourceConfigurationState`) call +`Get-SdnResource` (imported from `SdnDiag.NetworkController`) and `Trace-Output` (from +`SdnDiag.Utilities`). Both are available in `SdnDiag.Health`'s session state because Health +explicitly imports those modules at the top of `SdnDiag.Health.psm1`. + +Mock `Get-SdnResource` inside `InModuleScope SdnDiag.Health` to intercept calls made by the +health functions, and then invoke the health functions directly to validate their returned result: + +```powershell +Describe 'Health - Test-SdnResourceProvisioningState' { + It "Returns FAIL for a resource with Failed provisioning state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N04'] + } + $result = Test-SdnResourceProvisioningState -Resource 'Servers' -ResourceId 'DVLAB-S1-N04' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'FAIL' + } + } + + It "Returns PASS for a resource with Succeeded provisioning state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N01'] + } + $result = Test-SdnResourceProvisioningState -Resource 'Servers' -ResourceId 'DVLAB-S1-N01' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'PASS' + } + } +} +``` + +**Why this pattern works:** `SdnDiag.Health.psm1` imports `SdnDiag.NetworkController.psm1` at module +load time, so `Get-SdnResource` is available in Health's session state. `Mock Get-SdnResource` +inside `InModuleScope SdnDiag.Health` replaces it in that scope, intercepting calls from health +functions. The health function logic (switch statements, result assignment, remediation) then runs +against the mocked data and returns a real health-test object that tests can assert against. + +#### Pattern D: Remote command functions + +For functions using `Invoke-PSRemoteCommand`: + +```powershell +Describe 'Server - Get-SdnMyRemoteData' { + It "Returns remote data" { + InModuleScope SdnDiag.Server { + Mock Invoke-PSRemoteCommand { + return @{ Status = "OK"; Data = "mocked" } + } + $result = Get-SdnMyRemoteData -ComputerName "DVLAB-S1-N01" + $result.Status | Should -Be "OK" + } + } +} +``` + +### Step 5: Add Mock Data (if needed) + +If your test needs data not currently in `data/SdnApiResources/`: + +1. **Edit the appropriate JSON file** in `tests/offline/data/SdnApiResources/` +2. **Follow naming conventions:** + - Deployment prefix: `DVLAB` + - Domain: `dvlab.contoso.local` + - Servers: `DVLAB-S1-N01` through `DVLAB-S1-N04` + - Network Controllers: `DVLAB-NC01` through `DVLAB-NC03` + - Gateways: `DVLAB-GW01` through `DVLAB-GW03` + - Muxes: `DVLAB-MUX01` through `DVLAB-MUX02` +3. **Keep names consistent** — if you reference `DVLAB-S1-N01` in one file, use the same name everywhere +4. **IP addresses** may use any RFC1918 range — they don't need randomizing +5. **Never use real customer data** — use the `DVLAB` prefix pattern + +#### Adding a new resource type + +If you need a resource type not currently in the data folder: + +```json +{ + "value": [ + { + "resourceRef": "/yourResourceType/resource-id-0001", + "resourceId": "resource-id-0001", + "etag": "W/\"your-etag-0001\"", + "instanceId": "your-instance-0001-aaaa-bbbb-cccccccccccc", + "properties": { + "provisioningState": "Succeeded" + } + } + ], + "nextLink": "" +} +``` + +The file name (minus `.json`) becomes the key in `$Global:PesterOfflineTests.SdnApiResources`. + +### Step 6: Run Your Tests + +```powershell +# Run all offline tests +cd tests\offline +.\RunTests.ps1 + +# Run a specific test file +.\RunTests.ps1 -TestFile ".\Utilities.Tests.ps1" +``` + +**Prerequisites:** +- Pester v5+: `Install-Module -Name Pester -Force -SkipPublisherCheck` +- Build the module first: run the build script to populate `out/build/` + +## Test Design Guidelines + +1. **One behavior per `It` block** — test one logical behavior; multiple related assertions on the same result are fine (e.g., checking both count and a property) +2. **Test both happy path and error cases** — include boundary conditions +3. **Use descriptive test names** — describe what the test validates, not how +4. **Include a Failed/Unhealthy resource** in mock data — tests should validate detection of problems +5. **Don't depend on test execution order** — each `Describe` block should be independent +6. **Mock + call inside the same InModuleScope block** — never separate them +7. **Use `@(...)` for counts** — when filtering with `Where-Object`, wrap in `@()` before checking `.Count` (single-result gotcha) + +## Mock Data Reference + +### Current test environment (DVLAB) + +| Resource | Count | Names | +|----------|-------|-------| +| Servers | 4 | DVLAB-S1-N01 through N04 (N04 is in Failed state) | +| Gateways | 3 | DVLAB-GW01 through GW03 | +| Muxes | 2 | DVLAB-MUX01, DVLAB-MUX02 | +| Virtual Servers | 5 | DVLAB-GW01–03, DVLAB-MUX01–02 | +| Network Interfaces | 4 | tenantvm1, tenantvm2, nic-vm01-0001, nic-vm02-0002 | +| Load Balancers | 1 | lb-outbound-0001 (with OutboundNatPool) | +| Virtual Networks | 1 | vnet-0001 (192.168.33.0/24) | +| Public IPs | 3 | gw-vip-0001, pip-tenant-0001, pip-outbound-0001 | + +### Key test scenarios built into mock data + +- **Happy path:** DVLAB-S1-N01 through N03 are healthy (Succeeded/Success) +- **Failure detection:** DVLAB-S1-N04 has `provisioningState: Failed` and `configurationState: Failure` +- **Outbound NAT:** tenantvm2 is in `OutboundNatPool` → resolves to pip-outbound-0001 (40.40.40.4) +- **Direct VIP:** tenantvm1 has publicIPAddress → resolves to pip-tenant-0001 (40.40.40.5) + diff --git a/tests/README.md b/tests/README.md index e34d7624..7a67a9d1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,34 +2,26 @@ The `tests` folder include all the test script use [Pester](https://github.com/pester/Pester). -## Offline and Online Tests +## Offline Tests -The tests are categorized into two type of tests **offline** and **online** - -- **offline** test can be run without real SDN deployment through mock based on sample data collected from SDN deployment. -- **online** test need to run against SDN deployment +All tests are **offline** — they run without a real SDN deployment by mocking external calls with sample data. ## Folder Structure -- `offline\RunTests.ps1` is the start script to run all offline tests under offline test folder. -- `online\RunTests.ps1` is the start script to run all online tests under online folder. -- `wave1`... `waveAll` include all test scripts grouped into different wave. Tests will be executed in order of wave. +- `offline\RunTests.ps1` is the start script to run all offline tests under the offline test folder. +- `offline\data\` contains mock JSON data loaded into `$Global:PesterOfflineTests` ## Run offline tests - Install latest Pester by `Install-Module -Name Pester -Force -SkipPublisherCheck`. More info from [Pester Update](https://pester-docs.netlify.app/docs/introduction/installation) -- The `offline\data` folder include the sample data like `SdnApiResources`. The data is loaded into `$Global:PesterOfflineTest` +- Build the module first: run `.\build.ps1` from the repo root to populate `out/build/` +- The `offline\data` folder include the sample data like `SdnApiResources`. The data is loaded into `$Global:PesterOfflineTests` - Run offline test at offline folder by `.\RunTests.ps1` - -## Run online tests in your test environment - -- Generate the configuration based on `SdnDiagnosticsTestConfig-Sample.psd1`. Do not commit change to include your test environment specific settings. -- Copy the `tests` folder to the test environment and run - - `.\RunTests.ps1 -ConfigurationFile SdnDiagnosticsTestConfig-Sample.psd1` +- Run a specific test file: `.\RunTests.ps1 -TestFile ".\Utilities.Tests.ps1"` ## To create new tests -- If your test function can be mocked with sample data, put it under `offline` folder. Otherwise, this have to be under `online` folder. -- For offline test, sample data can be consumed from `$Global:PesterOfflineTest` to write your mock. -- The new test script should be named as `*originalscriptname*.Tests.ps1`. For example, `Diagnostics.Tests.ps1` include the tests function for script `Diagnostics.ps1` -- The online test scripts are grouped into different wave to maintain test execution order. `wave1` ... `waveAll` . If you don't expect order of test execution, the test script need to be in `waveAll` folder. +See [CONTRIBUTING_TESTS.md](CONTRIBUTING_TESTS.md) for detailed instructions on adding new tests, including: +- How to structure test files +- How to write mocks for different function patterns +- How to add or modify mock data +- Naming conventions for the test environment (DVLAB prefix) \ No newline at end of file diff --git a/tests/offline/Health.Tests.ps1 b/tests/offline/Health.Tests.ps1 new file mode 100644 index 00000000..c1d8fc99 --- /dev/null +++ b/tests/offline/Health.Tests.ps1 @@ -0,0 +1,97 @@ +# Health.Tests.ps1 +# +# Tests exercise the actual health validation functions in SdnDiag.Health +# (Test-SdnResourceProvisioningState and Test-SdnResourceConfigurationState) +# through InModuleScope SdnDiag.Health with a mocked Get-SdnResource, so +# regressions in the health logic are caught by CI. + +Describe 'Health - Test-SdnResourceProvisioningState' { + It "Returns FAIL when resource has Failed provisioning state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N04'] + } + $result = Test-SdnResourceProvisioningState -Resource 'Servers' -ResourceId 'DVLAB-S1-N04' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'FAIL' + } + } + + It "Returns PASS when resource has Succeeded provisioning state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N01'] + } + $result = Test-SdnResourceProvisioningState -Resource 'Servers' -ResourceId 'DVLAB-S1-N01' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'PASS' + } + } +} + +Describe 'Health - Test-SdnResourceConfigurationState' { + It "Returns PASS when provisioningState is not Succeeded (guard clause skips config check)" { + InModuleScope SdnDiag.Health { + # DVLAB-S1-N04 has provisioningState: Failed, so config state check is skipped + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N04'] + } + $result = Test-SdnResourceConfigurationState -Resource 'Servers' -ResourceId 'DVLAB-S1-N04' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'PASS' + } + } + + It "Returns PASS when resource has Success configuration state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return $Global:PesterOfflineTests.SdnApiResourcesByRef['/servers/DVLAB-S1-N01'] + } + $result = Test-SdnResourceConfigurationState -Resource 'Servers' -ResourceId 'DVLAB-S1-N01' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'PASS' + } + } + + It "Returns FAIL when resource has Failure configuration state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return [PSCustomObject]@{ + resourceRef = '/servers/test-failed-config' + resourceId = 'test-failed-config' + properties = [PSCustomObject]@{ + provisioningState = 'Succeeded' + configurationState = [PSCustomObject]@{ + status = 'Failure' + detailedInfo = @( + [PSCustomObject]@{ + code = 'HostUnreachable' + source = 'SoftwareLoadBalancerManager' + message = 'Host is unreachable.' + } + ) + } + } + } + } + $result = Test-SdnResourceConfigurationState -Resource 'Servers' -ResourceId 'test-failed-config' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'FAIL' + } + } + + It "Returns WARNING when resource has Warning configuration state" { + InModuleScope SdnDiag.Health { + Mock Get-SdnResource { + return [PSCustomObject]@{ + resourceRef = '/servers/test-warning-config' + resourceId = 'test-warning-config' + properties = [PSCustomObject]@{ + provisioningState = 'Succeeded' + configurationState = [PSCustomObject]@{ + status = 'Warning' + detailedInfo = @() + } + } + } + } + $result = Test-SdnResourceConfigurationState -Resource 'Servers' -ResourceId 'test-warning-config' -NcUri 'https://dvlab-nc.dvlab.contoso.local' + $result.Result | Should -Be 'WARNING' + } + } +} diff --git a/tests/offline/NetworkController.Tests.ps1 b/tests/offline/NetworkController.Tests.ps1 index 3b881b64..321849f4 100644 --- a/tests/offline/NetworkController.Tests.ps1 +++ b/tests/offline/NetworkController.Tests.ps1 @@ -1,18 +1,147 @@ -Describe 'NetworkController test' { - BeforeAll { - Mock -ModuleName SdnDiagnostics Get-SdnResource { - return $Global:PesterOfflineTests.SdnApiResources[$ResourceType.ToString()] +Describe 'NetworkController - Get-SdnServer' { + It "Returns server resources with resourceRef populated" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $servers = Get-SdnServer -NcUri "https://dvlab-nc.dvlab.contoso.local" + $servers.Count | Should -BeGreaterThan 0 + $servers[0].resourceRef | Should -Not -BeNullOrEmpty } } - It "Get-SdnServer -ManagementAddressOnly should return Server Address Only" { - $servers = Get-SdnServer "https://sdnexpnc" -ManagementAddressOnly - $servers.Count | Should -BeGreaterThan 0 - $servers[0].GetType() | Should -Be "String" + + It "Returns management addresses as strings with -ManagementAddressOnly" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $servers = Get-SdnServer -NcUri "https://dvlab-nc.dvlab.contoso.local" -ManagementAddressOnly + $servers.Count | Should -BeGreaterThan 0 + $servers[0].GetType().Name | Should -Be "String" + } + } + + It "Returns all 4 servers from mock data" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $servers = Get-SdnServer -NcUri "https://dvlab-nc.dvlab.contoso.local" + $servers.Count | Should -Be 4 + } + } +} + +Describe 'NetworkController - Get-SdnGateway' { + It "Returns gateway resources" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $gateways = Get-SdnGateway -NcUri "https://dvlab-nc.dvlab.contoso.local" + $gateways.Count | Should -Be 3 + $gateways[0].resourceRef | Should -Not -BeNullOrEmpty + } + } + + It "Returns management addresses as strings with -ManagementAddressOnly" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $gateways = Get-SdnGateway -NcUri "https://dvlab-nc.dvlab.contoso.local" -ManagementAddressOnly + $gateways.Count | Should -BeGreaterThan 0 + $gateways[0].GetType().Name | Should -Be "String" + } } +} - It "Get-SdnServer should return Server resource" { - $servers = Get-SdnServer "https://sdnexpnc" - $servers.Count | Should -BeGreaterThan 0 - $servers[0].resourceRef | Should -Not -BeNullOrEmpty +Describe 'NetworkController - Get-SdnLoadBalancerMux' { + It "Returns load balancer mux resources" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $muxes = Get-SdnLoadBalancerMux -NcUri "https://dvlab-nc.dvlab.contoso.local" + $muxes.Count | Should -Be 2 + $muxes[0].resourceRef | Should -Not -BeNullOrEmpty + } + } + + It "Returns management addresses as strings with -ManagementAddressOnly" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $muxes = Get-SdnLoadBalancerMux -NcUri "https://dvlab-nc.dvlab.contoso.local" -ManagementAddressOnly + $muxes.Count | Should -BeGreaterThan 0 + $muxes[0].GetType().Name | Should -Be "String" + } + } +} + +Describe 'NetworkController - Get-SdnResource' { + It "Returns servers when Resource is Servers" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $result = Get-SdnResource -NcUri "https://dvlab-nc.dvlab.contoso.local" -Resource Servers + $result.Count | Should -BeGreaterThan 0 + } + } + + It "Returns gateways when Resource is Gateways" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $result = Get-SdnResource -NcUri "https://dvlab-nc.dvlab.contoso.local" -Resource Gateways + $result.Count | Should -BeGreaterThan 0 + } } - } +} diff --git a/tests/offline/RunTests.ps1 b/tests/offline/RunTests.ps1 index d80a6d86..377c802e 100644 --- a/tests/offline/RunTests.ps1 +++ b/tests/offline/RunTests.ps1 @@ -1,4 +1,11 @@ # Load the baseline test data needed for Pester Mock +param( + [Parameter(Mandatory = $false)] + [string[]]$Tag, + + [Parameter(Mandatory = $false)] + [string]$TestFile +) $modulePath = Get-Item -Path "$PSScriptRoot\..\..\out\build\SdnDiagnostics\SdnDiagnostics.psd1" -ErrorAction SilentlyContinue if($null -eq $modulePath){ @@ -10,9 +17,16 @@ if($null -eq $modulePath){ $sdnApiResourcesPath = "$PSScriptRoot\data\SdnApiResources" $Global:PesterOfflineTests = @{} $Global:PesterOfflineTests.SdnApiResources = @{} -foreach($file in Get-ChildItem -Path $sdnApiResourcesPath) +foreach($file in Get-ChildItem -Path $sdnApiResourcesPath -Filter "*.json") { - $Global:PesterOfflineTests.SdnApiResources[$file.BaseName] = Get-Content -Path $file.FullName | ConvertFrom-Json + $content = Get-Content -Path $file.FullName -Raw | ConvertFrom-Json + # Handle both wrapped {value:[...]} and raw array formats + if ($null -ne $content.value) { + $Global:PesterOfflineTests.SdnApiResources[$file.BaseName] = $content.value + } + else { + $Global:PesterOfflineTests.SdnApiResources[$file.BaseName] = $content + } } $Global:PesterOfflineTests.SdnApiResourcesByRef = [System.Collections.Hashtable]::new() @@ -22,11 +36,30 @@ foreach($resourceType in $Global:PesterOfflineTests.SdnApiResources.Keys) foreach($resource in $resourcesOfType) { if($null -ne $resource.resourceRef){ - $Global:PesterOfflineTests.SdnApiResourcesByRef.Add($resource.resourceRef, $resource) + $Global:PesterOfflineTests.SdnApiResourcesByRef[$resource.resourceRef] = $resource } } } Import-Module -Name $modulePath.FullName -Force -Invoke-Pester "$PSScriptRoot\*Tests.ps1" -Output Detailed \ No newline at end of file +# Build Pester parameters +$pesterParams = @{ + Output = 'Detailed' +} + +if ($TestFile) { + $pesterParams.Path = $TestFile +} +else { + $pesterParams.Path = "$PSScriptRoot\*Tests.ps1" +} + +if ($Tag) { + $pesterParams.TagFilter = $Tag +} + +$results = Invoke-Pester @pesterParams -PassThru +if ($results.Result -ne 'Passed') { + throw "$($results.FailedCount) Pester test(s) failed. Result: $($results.Result)" +} \ No newline at end of file diff --git a/tests/offline/SoftwareLoadBalancer.Tests.ps1 b/tests/offline/SoftwareLoadBalancer.Tests.ps1 index b0b5d9cb..6b6bb31e 100644 --- a/tests/offline/SoftwareLoadBalancer.Tests.ps1 +++ b/tests/offline/SoftwareLoadBalancer.Tests.ps1 @@ -1,23 +1,39 @@ Describe 'LoadBalancerMux test' { - BeforeAll { - Mock -ModuleName SdnDiagnostics Get-SdnResource { - if(![string]::IsNullOrEmpty($ResourceRef)){ - return $Global:PesterOfflineTests.SdnApiResourcesByRef[$ResourceRef] - } - else { - return $Global:PesterOfflineTests.SdnApiResources[$ResourceType.ToString()] + It "Get-SdnNetworkInterfaceOutboundPublicIPAddress able to return Public VIP from Outbound NAT Rule" { + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } } + $publicIpInfo = Get-SdnNetworkInterfaceOutboundPublicIPAddress -NcUri "https://dvlab-nc.dvlab.contoso.local" -ResourceId tenantvm2 + $publicIpInfo.PublicIPAddress | Should -Be "40.40.40.4" + $publicIpInfo.IPConfigPrivateIPAddress | Should -Be "192.168.33.5" } } - It "Get-SdnNetworkInterfaceOutboundPublicIPAddress able to return Public VIP from Outbound NAT Rule" { - $publicIpInfo = Get-SdnNetworkInterfaceOutboundPublicIPAddress -NcUri "https://sdnexpnc" -ResourceId tenantvm2 - $publicIpInfo.PublicIPAddress | Should -Be "40.40.40.4" - $publicIpInfo.IPConfigPrivateIPAddress | Should -Be "192.168.33.5" - } It "Get-SdnNetworkInterfaceOutboundPublicIPAddress able to return Public VIP on network interface" { - $publicIpInfo = Get-SdnNetworkInterfaceOutboundPublicIPAddress -NcUri "https://sdnexpnc" -ResourceId tenantvm1 - $publicIpInfo.PublicIPAddress | Should -Be "40.40.40.5" - $publicIpInfo.IPConfigPrivateIPAddress | Should -Be "192.168.33.4" + InModuleScope SdnDiag.NetworkController { + Mock Invoke-RestMethodWithRetry { + $path = ([Uri]$Uri).AbsolutePath + if ($path -match '/networking/v1/(.+)$') { + $resourceType = ($Matches[1] -split '/')[0] + $refKey = "/$($Matches[1])" + if ($Global:PesterOfflineTests.SdnApiResourcesByRef.ContainsKey($refKey)) { + return $Global:PesterOfflineTests.SdnApiResourcesByRef[$refKey] + } + return [PSCustomObject]@{ value = $Global:PesterOfflineTests.SdnApiResources[$resourceType] } + } + } + $publicIpInfo = Get-SdnNetworkInterfaceOutboundPublicIPAddress -NcUri "https://dvlab-nc.dvlab.contoso.local" -ResourceId tenantvm1 + $publicIpInfo.PublicIPAddress | Should -Be "40.40.40.5" + $publicIpInfo.IPConfigPrivateIPAddress | Should -Be "192.168.33.4" + } } - } +} diff --git a/tests/offline/Utilities.Tests.ps1 b/tests/offline/Utilities.Tests.ps1 new file mode 100644 index 00000000..a00aef64 --- /dev/null +++ b/tests/offline/Utilities.Tests.ps1 @@ -0,0 +1,179 @@ +Describe 'Utilities - Format Functions' { + Context 'Format-MacAddressWithDashes' { + It "Converts 12-char MAC to dashed format" { + InModuleScope SdnDiagnostics { + $result = Format-MacAddressWithDashes -MacAddress "001DD8070001" + $result | Should -Be "00-1D-D8-07-00-01" + } + } + + It "Normalizes lowercase to uppercase" { + InModuleScope SdnDiagnostics { + $result = Format-MacAddressWithDashes -MacAddress "001dd8070001" + $result | Should -Be "00-1D-D8-07-00-01" + } + } + + It "Passes through already-dashed MAC unchanged (uppercased)" { + InModuleScope SdnDiagnostics { + $result = Format-MacAddressWithDashes -MacAddress "00-1D-D8-07-00-01" + $result | Should -Be "00-1D-D8-07-00-01" + } + } + + It "Throws on invalid length (not 12 chars, no dashes)" { + InModuleScope SdnDiagnostics { + { Format-MacAddressWithDashes -MacAddress "001DD807" } | Should -Throw + } + } + + It "Throws on invalid dashed format (wrong segment length)" { + InModuleScope SdnDiagnostics { + { Format-MacAddressWithDashes -MacAddress "001-DD8-070-001-00-01" } | Should -Throw + } + } + } + + Context 'Format-MacAddressNoDashes' { + It "Removes dashes from valid MAC address" { + InModuleScope SdnDiagnostics { + $result = Format-MacAddressNoDashes -MacAddress "00-1D-D8-07-00-01" + $result | Should -Be "001DD8070001" + } + } + + It "Returns uppercase when already no dashes" { + InModuleScope SdnDiagnostics { + $result = Format-MacAddressNoDashes -MacAddress "001dd8070001" + $result | Should -Be "001DD8070001" + } + } + + It "Throws on invalid dashed format (wrong segment length)" { + InModuleScope SdnDiagnostics { + { Format-MacAddressNoDashes -MacAddress "001-DD8-070-001-00-01" } | Should -Throw + } + } + } + + Context 'Format-SdnMacAddress' { + It "Without -Dashes returns no-dash format" { + InModuleScope SdnDiagnostics { + $result = Format-SdnMacAddress -MacAddress "00-1D-D8-07-00-01" + $result | Should -Be "001DD8070001" + } + } + + It "With -Dashes returns dashed format" { + InModuleScope SdnDiagnostics { + $result = Format-SdnMacAddress -MacAddress "001DD8070001" -Dashes + $result | Should -Be "00-1D-D8-07-00-01" + } + } + } + + Context 'Format-ByteSize' { + It "Converts bytes to GB and MB" { + InModuleScope SdnDiagnostics { + $result = Format-ByteSize -Bytes 1073741824 + $result.GB | Should -Be "1" + $result.MB | Should -Be "1024" + } + } + + It "Handles zero bytes" { + InModuleScope SdnDiagnostics { + $result = Format-ByteSize -Bytes 0 + $result.GB | Should -Be "0" + $result.MB | Should -Be "0" + } + } + } + + Context 'Format-KiloBitSize' { + It "Converts kilobits to GB and MB" { + InModuleScope SdnDiagnostics { + $result = Format-KiloBitSize -KiloBits 1000000 + $result.GB | Should -Be "1" + $result.MB | Should -Be "1000" + } + } + + It "Handles zero kilobits" { + InModuleScope SdnDiagnostics { + $result = Format-KiloBitSize -KiloBits 0 + $result.GB | Should -Be "0" + $result.MB | Should -Be "0" + } + } + } +} + +Describe 'Utilities - IP Address Validation' { + Context 'Confirm-IpAddressInRange' { + It "Returns true when IP is within range" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInRange -IpAddress "192.168.1.50" -StartAddress "192.168.1.1" -EndAddress "192.168.1.100" + $result | Should -BeTrue + } + } + + It "Returns true when IP equals start address" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInRange -IpAddress "192.168.1.1" -StartAddress "192.168.1.1" -EndAddress "192.168.1.100" + $result | Should -BeTrue + } + } + + It "Returns false when IP is above range" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInRange -IpAddress "192.168.1.101" -StartAddress "192.168.1.1" -EndAddress "192.168.1.100" + $result | Should -BeFalse + } + } + + It "Returns false when IP is null or empty" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInRange -IpAddress "" -StartAddress "192.168.1.1" -EndAddress "192.168.1.100" + $result | Should -BeFalse + } + } + } + + Context 'Confirm-IpAddressInCidrRange' { + It "Returns true for IP within /24 network" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInCidrRange -IpAddress "10.20.30.50" -Cidr "10.20.30.0/24" + $result | Should -BeTrue + } + } + + It "Returns false for IP outside /24 network" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInCidrRange -IpAddress "10.20.31.1" -Cidr "10.20.30.0/24" + $result | Should -BeFalse + } + } + + It "Returns true for exact match on /32" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInCidrRange -IpAddress "10.20.30.5" -Cidr "10.20.30.5/32" + $result | Should -BeTrue + } + } + + It "Returns false for non-match on /32" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInCidrRange -IpAddress "10.20.30.6" -Cidr "10.20.30.5/32" + $result | Should -BeFalse + } + } + + It "Returns true for any IP within /0 (matches all addresses)" { + InModuleScope SdnDiagnostics { + $result = Confirm-IpAddressInCidrRange -IpAddress "192.168.1.1" -Cidr "0.0.0.0/0" + $result | Should -BeTrue + } + } + } +} diff --git a/tests/offline/data/SdnApiResources/accessControlLists.json b/tests/offline/data/SdnApiResources/accessControlLists.json index 47356d0b..47506bc8 100644 Binary files a/tests/offline/data/SdnApiResources/accessControlLists.json and b/tests/offline/data/SdnApiResources/accessControlLists.json differ diff --git a/tests/offline/data/SdnApiResources/credentials.json b/tests/offline/data/SdnApiResources/credentials.json index a88c5639..e5dbe48c 100644 Binary files a/tests/offline/data/SdnApiResources/credentials.json and b/tests/offline/data/SdnApiResources/credentials.json differ diff --git a/tests/offline/data/SdnApiResources/gatewayPools.json b/tests/offline/data/SdnApiResources/gatewayPools.json index ba2d4588..20201e9d 100644 Binary files a/tests/offline/data/SdnApiResources/gatewayPools.json and b/tests/offline/data/SdnApiResources/gatewayPools.json differ diff --git a/tests/offline/data/SdnApiResources/gateways.json b/tests/offline/data/SdnApiResources/gateways.json index e6776258..5fcc8b36 100644 Binary files a/tests/offline/data/SdnApiResources/gateways.json and b/tests/offline/data/SdnApiResources/gateways.json differ diff --git a/tests/offline/data/SdnApiResources/iDNSServer_configuration.json b/tests/offline/data/SdnApiResources/iDNSServer_configuration.json index e69de29b..8ca4e748 100644 --- a/tests/offline/data/SdnApiResources/iDNSServer_configuration.json +++ b/tests/offline/data/SdnApiResources/iDNSServer_configuration.json @@ -0,0 +1,20 @@ +{ + "properties": { + "forwarders": [ + "168.63.129.16" + ], + "provisioningState": "Succeeded", + "connections": [ + { + "managementAddresses": [ + "10.20.30.50" + ], + "credential": { + "resourceRef": "/credentials/iDnsServer-Credential" + }, + "credentialType": "UsernamePassword" + } + ], + "zone": "dvlab.contoso.local" + } +} diff --git a/tests/offline/data/SdnApiResources/loadBalancerManager_config.json b/tests/offline/data/SdnApiResources/loadBalancerManager_config.json index e6eddef7..0c7b6cac 100644 Binary files a/tests/offline/data/SdnApiResources/loadBalancerManager_config.json and b/tests/offline/data/SdnApiResources/loadBalancerManager_config.json differ diff --git a/tests/offline/data/SdnApiResources/loadBalancerMuxes.json b/tests/offline/data/SdnApiResources/loadBalancerMuxes.json index 7be76ef6..1853d035 100644 Binary files a/tests/offline/data/SdnApiResources/loadBalancerMuxes.json and b/tests/offline/data/SdnApiResources/loadBalancerMuxes.json differ diff --git a/tests/offline/data/SdnApiResources/loadBalancers.json b/tests/offline/data/SdnApiResources/loadBalancers.json index 1c529251..b2eefb72 100644 Binary files a/tests/offline/data/SdnApiResources/loadBalancers.json and b/tests/offline/data/SdnApiResources/loadBalancers.json differ diff --git a/tests/offline/data/SdnApiResources/logicalNetworks.json b/tests/offline/data/SdnApiResources/logicalNetworks.json index f2343680..23afffa4 100644 Binary files a/tests/offline/data/SdnApiResources/logicalNetworks.json and b/tests/offline/data/SdnApiResources/logicalNetworks.json differ diff --git a/tests/offline/data/SdnApiResources/macPools.json b/tests/offline/data/SdnApiResources/macPools.json index 160b60b7..1b67cb00 100644 Binary files a/tests/offline/data/SdnApiResources/macPools.json and b/tests/offline/data/SdnApiResources/macPools.json differ diff --git a/tests/offline/data/SdnApiResources/networkInterfaces.json b/tests/offline/data/SdnApiResources/networkInterfaces.json index 7f9adf24..0330cb05 100644 Binary files a/tests/offline/data/SdnApiResources/networkInterfaces.json and b/tests/offline/data/SdnApiResources/networkInterfaces.json differ diff --git a/tests/offline/data/SdnApiResources/publicIPAddresses.json b/tests/offline/data/SdnApiResources/publicIPAddresses.json index 6c9638e9..5bde3db2 100644 Binary files a/tests/offline/data/SdnApiResources/publicIPAddresses.json and b/tests/offline/data/SdnApiResources/publicIPAddresses.json differ diff --git a/tests/offline/data/SdnApiResources/routeTables.json b/tests/offline/data/SdnApiResources/routeTables.json index 47356d0b..be10782d 100644 Binary files a/tests/offline/data/SdnApiResources/routeTables.json and b/tests/offline/data/SdnApiResources/routeTables.json differ diff --git a/tests/offline/data/SdnApiResources/servers.json b/tests/offline/data/SdnApiResources/servers.json index 3131b67d..a23de120 100644 Binary files a/tests/offline/data/SdnApiResources/servers.json and b/tests/offline/data/SdnApiResources/servers.json differ diff --git a/tests/offline/data/SdnApiResources/virtualGateways.json b/tests/offline/data/SdnApiResources/virtualGateways.json index 71b2b903..8f7d40c9 100644 Binary files a/tests/offline/data/SdnApiResources/virtualGateways.json and b/tests/offline/data/SdnApiResources/virtualGateways.json differ diff --git a/tests/offline/data/SdnApiResources/virtualNetworkManager_configuration.json b/tests/offline/data/SdnApiResources/virtualNetworkManager_configuration.json index b1135c56..0ecd6798 100644 Binary files a/tests/offline/data/SdnApiResources/virtualNetworkManager_configuration.json and b/tests/offline/data/SdnApiResources/virtualNetworkManager_configuration.json differ diff --git a/tests/offline/data/SdnApiResources/virtualNetworks.json b/tests/offline/data/SdnApiResources/virtualNetworks.json index 120ae9f4..2851e40a 100644 Binary files a/tests/offline/data/SdnApiResources/virtualNetworks.json and b/tests/offline/data/SdnApiResources/virtualNetworks.json differ diff --git a/tests/offline/data/SdnApiResources/virtualServers.json b/tests/offline/data/SdnApiResources/virtualServers.json index da7e8e0f..54574e83 100644 Binary files a/tests/offline/data/SdnApiResources/virtualServers.json and b/tests/offline/data/SdnApiResources/virtualServers.json differ diff --git a/tests/online/RunTests.ps1 b/tests/online/RunTests.ps1 deleted file mode 100644 index 938c8efb..00000000 --- a/tests/online/RunTests.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -param( - [Parameter(Mandatory = $true)] - [String] $ConfigurationFile -) -$Global:PesterOnlineTests = @{ -} - -$Global:PesterOnlineTests.ConfigData = [hashtable] (Get-Content -Path $ConfigurationFile | Out-String) - -$Global:PesterOnlineTests.NcRestCredential = [System.Management.Automation.PSCredential]::Empty -#$ncAdminCredential = [System.Management.Automation.PSCredential]::Empty -if($null -ne $Global:PesterOnlineTests.ConfigData.NcRestCredentialUser){ - $ncRestSecurePassword = $Global:PesterOnlineTests.ConfigData.NcRestCredentialPassword | ConvertTo-SecureString - $Global:PesterOnlineTests.NcRestCredential = New-Object System.Management.Automation.PsCredential($Global:PesterOnlineTests.ConfigData.NcRestCredentialUser, $ncRestSecurePassword) -} - -if($null -eq $Global:PesterOnlineTests.ConfigData.SdnDiagnosticsModule) -{ - $modulePathFromBuild = "$PSScriptRoot\..\..\out\build\SdnDiagnostics\SdnDiagnostics.psd1" - "Importing module from {0}" -f $modulePathFromBuild | Write-Output - Import-Module $modulePathFromBuild -}else { - Import-Module $Global:PesterOnlineTests.ConfigData.SdnDiagnosticsModule -Force -} - -# Tests can be arranged in different wave if order matters -$testFailed = 0 -$testResult = Invoke-Pester "$PSScriptRoot\wave1\*Tests.ps1" -Output Detailed -PassThru -if($testResult.Result -ne "Passed") -{ - $testFailed = 1 -} -$testResult = Invoke-Pester "$PSScriptRoot\waveAll\*Tests.ps1" -Output Detailed -PassThru -if($testResult.Result -ne "Passed") -{ - $testFailed = 1 -} - -# Exit code 0 indicate success -return $testFailed diff --git a/tests/online/SdnDiagnosticsTestConfig-Sample.psd1 b/tests/online/SdnDiagnosticsTestConfig-Sample.psd1 deleted file mode 100644 index 28e5f838..00000000 --- a/tests/online/SdnDiagnosticsTestConfig-Sample.psd1 +++ /dev/null @@ -1,19 +0,0 @@ -@{ - # Required. Specify the one of NC VM Name for tests to start with - NcVM = 'sdnexpnc01.corp.contoso.com' - - # Configure NcRestCredential if needed - # NcRestCredentialUser = 'domain\user' - - # The Password need to be secure string from (Get-Credential).Password | ConvertFrom-SecureString - # NcRestCredentialPassword = 'YourPassword' - - # Required. Specify the SdnDiagnosticsModule Path - # SdnDiagnosticsModule = '' - - # The number of each infra node. This will ensure the module able to get information match the test environment. - NumberOfNc = 3 - NumberOfMux = 2 - NumberOfServer = 3 - NumberOfGateway = 2 -} \ No newline at end of file diff --git a/tests/online/wave1/Utilities.Tests.ps1 b/tests/online/wave1/Utilities.Tests.ps1 deleted file mode 100644 index 87c5d3aa..00000000 --- a/tests/online/wave1/Utilities.Tests.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -# Pester tests -Describe 'Install-SdnDiagnostics test' { - It "Install-SdnDiagnostics installed SdnDiagnostic Module successfully" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - Install-SdnDiagnostics -ComputerName $infraInfo.fabricNodes - - $currentModule = Get-Module SdnDiagnostics - - $remoteModuleInfo = Invoke-Command -ComputerName $infraInfo.fabricNodes -ScriptBlock{ - return (Get-Module -ListAvailable -Name SdnDiagnostics) - } - - foreach ($moduleInfo in $remoteModuleInfo) { - $moduleInfo.Version | Should -Be $currentModule.Version - } - #$infraInfo.NCUrl | Should -not -BeNullOrEmpty - } - } \ No newline at end of file diff --git a/tests/online/waveAll/Debug-SdnFabricInfrastructure.Tests.ps1 b/tests/online/waveAll/Debug-SdnFabricInfrastructure.Tests.ps1 deleted file mode 100644 index 6c7aedf2..00000000 --- a/tests/online/waveAll/Debug-SdnFabricInfrastructure.Tests.ps1 +++ /dev/null @@ -1,7 +0,0 @@ -# Pester tests -Describe 'Debug-SdnFabricInfrastructure test' { - It "Debug-SdnFabricInfrastrucure run all debug with no exception" { - $result = Debug-SdnFabricInfrastructure -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $result | Should -not -BeNullOrEmpty - } - } diff --git a/tests/online/waveAll/Get-SdnInfrastructureInfo.Tests.ps1 b/tests/online/waveAll/Get-SdnInfrastructureInfo.Tests.ps1 deleted file mode 100644 index 2a7bd26f..00000000 --- a/tests/online/waveAll/Get-SdnInfrastructureInfo.Tests.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -# Pester tests -Describe 'Get-SdnInfrastructureInfo test' { - It "Able to retreive NCUrl" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $infraInfo.NCUrl | Should -not -BeNullOrEmpty - } - It "All NC retrieved" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $infraInfo.NetworkController.Count | Should -Be $Global:PesterOnlineTests.ConfigData.NumberOfNc - } - It "All MUX retrieved" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $infraInfo.LoadBalancerMux.Count | Should -Be $Global:PesterOnlineTests.ConfigData.NumberOfMux - } - It "All Gateway retrieved" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $infraInfo.Gateway.Count | Should -Be $Global:PesterOnlineTests.ConfigData.NumberOfGateway - } - - It "All Server retrieved" { - $infraInfo = Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential - $infraInfo.Server.Count | Should -Be $Global:PesterOnlineTests.ConfigData.NumberOfServer - } -} diff --git a/tests/online/waveAll/Start-SdnDataCollection.Tests.ps1 b/tests/online/waveAll/Start-SdnDataCollection.Tests.ps1 deleted file mode 100644 index 5be7b3a3..00000000 --- a/tests/online/waveAll/Start-SdnDataCollection.Tests.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -# Pester tests -Describe 'Start-SdnDataCollection test' { - It "Start-SdnNetshTrace successfully started trace on Server" { - { Start-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).Server -Role Server} | Should -Not -Throw - } - - It "Start-SdnNetshTrace successfully started trace on Mux" { - { Start-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).LoadBalancerMux -Role LoadBalancerMux} | Should -Not -Throw - } - - It "Start-SdnNetshTrace successfully started trace on Gateway" { - { Start-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).Gateway -Role Gateway} | Should -Not -Throw - } - - Start-Sleep -Seconds 60 - - It "Stop-SdnNetshTrace successfully stop trace on Server" { - { Stop-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).Server} | Should -Not -Throw - } - - It "Stop-SdnNetshTrace successfully stop trace on LoadBalancerMux" { - { Stop-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).LoadBalancerMux} | Should -Not -Throw - } - - It "Stop-SdnNetshTrace successfully stop trace on Gateway" { - { Stop-SdnNetshTrace -ComputerName (Get-SdnInfrastructureInfo -NetworkController $Global:PesterOnlineTests.configdata.NcVM -NcRestCredential $Global:PesterOnlineTests.NcRestCredential).Gateway} | Should -Not -Throw - } - - It "Start-SdnDataCollection successfully collected the logs" { - { Start-SdnDataCollection -NetworkController $Global:PesterOnlineTests.configdata.NcVM -Role NetworkController,LoadBalancerMux,Gateway,Server -OutputDirectory "$PSScriptRoot\..\..\DataCollected" -IncludeLogs -NcRestCredential $Global:PesterOnlineTests.NcRestCredential } | Should -Not -Throw - } - }