Tests/SUCommonTestFuncs.psm1
function Invoke-MockWebRequest { param ( [Parameter(Mandatory = $true)] [string] $URI, [hashtable] $Headers, [string] $ContentType, [hashtable] $Body, [int] $MaximumRedirection, [boolean] $SkipHttpErrorCheck, [string] $Method = 'GET' ) $endpoint = $URI.Split('?')[0] $results = switch -Wildcard ($endpoint) { "https://accounts.spotify.com/authorize" { Invoke-MockAuthorize @PSBoundParameters } "https://accounts.spotify.com/api/token" { Invoke-MockToken @PSBoundParameters } "https://api.spotify.com/v1/me/tracks" { Invoke-MockTracks @PSBoundParameters } "https://api.spotify.com/v1/me/playlists" { Invoke-MockPlaylists @PSBoundParameters } "https://api.spotify.com/v1/playlists/*/tracks" { Invoke-MockTracks @PSBoundParameters } "https://api.spotify.com/v1/search" { Invoke-MockSearch @PSBoundParameters } Default { throw "Endpoint $endpoint un-mocked" } } return $results } function Invoke-MockAuthorize { param ( [Parameter(Mandatory = $true)] [string] $URI, [int] $MaximumRedirection, [boolean] $SkipHttpErrorCheck, [string] $Method = 'GET' ) if ($Method -ne 'GET') { throw "Wrong HTTP method (Expected GET, got $Method)" } $endpoint, $paramstring = $URI.Split('?') if ($paramstring) { $parameters = @{} foreach ($substring in $paramstring -split '&') { $values = $substring -split '=' $parameters.Add( $values[0], $values[1] ) } } $requiredParams = @( 'client_id', 'response_type', 'redirect_uri', 'code_challenge_method', 'code_challenge' ) foreach ($param in $requiredParams) { if (! $parameters.ContainsKey($param)) { throw "Request was missing a required parameter ($param)" } } return @{ StatusCode = 302 Headers = @{ # this will be invoked with our mocked Start-Process, which # needs to know the redirect URI Location = [array] $parameters['redirect_uri'] } } } function Invoke-MockToken { param ( [Parameter(Mandatory = $true)] [string] $URI, [string] $ContentType, [hashtable] $Body, [string] $Method = 'GET' ) if ($Method -ne 'POST') { throw "Wrong HTTP method (Expected POST, got $Method)" } if ($ContentType -ne 'application/x-www-form-urlencoded') { throw "Content-Type header must be equal to 'application/x-www-form-urlencoded'" } $requiredParams = @( 'grant_type', 'code', 'redirect_uri', 'client_id', 'code_verifier' ) foreach ($param in $requiredParams) { if (! $Body.ContainsKey($param)) { throw "Request was missing a required parameter ($param)" } } return @{ StatusCode = 200 Content = @{ access_token = 'mockedtoken' token_type = 'Bearer' scope = 'mockedscopes' expires_in = 3600 refresh_token = 'mockedrefreshtoken' } | ConvertTo-Json } } # normally used to open the Spotify authentication dialog in user's browser, # which then redirects to the specified URI with a token function Start-MockProcess { param ( [string] $FilePath ) $uri = "${FilePath}?code=mockedcode&state=mockedstate" $pwsh = [System.Diagnostics.Process]::GetCurrentProcess().Path $params = @{ FilePath = $pwsh ArgumentList = @('-c', "Start-Sleep 1; Invoke-WebRequest '$uri'") } if (! $IsLinux) { $params.Add('WindowStyle', 'Hidden') } Start-Process @params } function Get-MockContent { param ( [string] $Path ) switch ($Path) { { $_ -match ".*.env.json" } { return '{ "ClientID" : "mockedclientid", "RedirectURI" : "http://localhost:8080" }' } "mockfile.json" { return '{ "ClientID" : "mockedclientid", "RedirectURI" : "http://localhost:8080" }' } Default { return [System.IO.File]::ReadAllText($Path) } } } $trackResponseSample = @' { "href": "https://api.spotify.com/v1/me/tracks?offset=0&limit=20", "limit": 20, "next": "https://api.spotify.com/v1/me/tracks?offset=1&limit=1", "offset": 0, "previous": "https://api.spotify.com/v1/me/tracks?offset=1&limit=1", "total": 4, "items": [ { "added_at": "string", "track": { "album": { "album_type": "compilation", "total_tracks": 9, "available_markets": [ "CA", "BR", "IT" ], "external_urls": { "spotify": "string" }, "href": "string", "id": "2up3OPMp9Tb4dAKM2erWXQ", "images": [ { "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228", "height": 300, "width": 300 } ], "name": "album name", "release_date": "1981-12", "release_date_precision": "year", "restrictions": { "reason": "market" }, "type": "album", "uri": "spotify:album:2up3OPMp9Tb4dAKM2erWXQ", "artists": [ { "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "name": "string", "type": "artist", "uri": "string" } ] }, "artists": [ { "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "name": "artist1", "type": "artist", "uri": "string" }, { "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "name": "artist2", "type": "artist", "uri": "string" } ], "available_markets": [ "string" ], "disc_number": 0, "duration_ms": 0, "explicit": false, "external_ids": { "isrc": "string", "ean": "string", "upc": "string" }, "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "is_playable": false, "linked_from": {}, "restrictions": { "reason": "string" }, "name": "song name", "popularity": 0, "preview_url": "string", "track_number": 0, "type": "track", "uri": "string", "is_local": false } } ] } '@ $trackSampleConverted = ` '{"name":"song name","album":"album name","artists":["artist1","artist2"]}' function Get-TrackSample { return $trackResponseSample } function Get-TrackConvertedSample { return $trackSampleConverted } function Invoke-MockTracks { param ( [Parameter(Mandatory = $true)] [string] $URI, [hashtable] $Headers, [hashtable] $Body, [string] $ContentType, [string] $Method = 'GET' ) $authorized = ($Headers.ContainsKey('Authorization') -and $Headers.Authorization -match 'Bearer *') if (! $authorized) { throw 'Authorization header invalid' } if ($Method -eq 'GET') { $tracks = $trackResponseSample | ConvertFrom-Json # prevent method from trying to retrieve another page of results $tracks.next = $null # make array more than one item long to prevent PS unpacking $tracks.items = @($tracks.items[0], $tracks.items[0]) $result = @{ StatusCode = 200 Content = $tracks | ConvertTo-Json -Depth 10 } } elseif ($Method -eq 'PUT') { if (! $Body.ids -or ! $Body.ids.Count) { throw "'ids' required in body of request" } $result = @{ StatusCode = 200 } } else { throw "Invalid method ($method) for URI ($URI)" } return $result } $playlistResponseSample = @' { "href": "https://api.spotify.com/v1/me/playlists?offset=0&limit=20", "limit": 20, "next": "https://api.spotify.com/v1/me/playlists?offset=1&limit=1", "offset": 0, "previous": "https://api.spotify.com/v1/me/playlists?offset=1&limit=1", "total": 4, "items": [ { "collaborative": false, "description": "string", "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "images": [ { "url": "https://i.scdn.co/image/ab67616d00001e02ff9ca10b55ce82ae553c8228", "height": 300, "width": 300 } ], "name": "playlist name", "owner": { "external_urls": { "spotify": "string" }, "href": "string", "id": "string", "type": "user", "uri": "string", "display_name": "playlist owner name" }, "public": false, "snapshot_id": "string", "tracks": { "href": "string", "total": 0 }, "type": "string", "uri": "string" } ] } '@ # only one copy of 'trackSampleConverted' despite using two in # Invoke-MockPlaylists # ConvertTo-SpotifyTracks will de-duplicate $singlePlaylistOutput = @" {"Name":"playlist name","Owner":"playlist owner name","Tracks":[$trackSampleConverted]} "@ $playlistOutput = @" [$singlePlaylistOutput,$singlePlaylistOutput] "@ function Get-PlaylistOutput { return $playlistOutput } function Invoke-MockPlaylists { param ( [Parameter(Mandatory = $true)] [string] $URI, [hashtable] $Headers ) $authorized = ($Headers.ContainsKey('Authorization') -and $Headers.Authorization -match 'Bearer *') if (! $authorized) { throw 'Authorization header invalid' } $plists = $playlistResponseSample | ConvertFrom-Json # prevent method from trying to retrieve another page of results $plists.next = $null # ensure the tracks href will trigger a mocked API $plists.items[0].tracks.href = "https://api.spotify.com/v1/playlists/$([guid]::NewGuid())/tracks" # make array more than one item long to prevent PS unpacking $plists.items = @($plists.items[0], $plists.items[0]) return @{ StatusCode = 200 Content = $plists | ConvertTo-Json -Depth 100 } } function Invoke-MockSearch { param ( [Parameter(Mandatory = $true)] [string] $URI, [hashtable] $Headers ) $authorized = ($Headers.ContainsKey('Authorization') -and $Headers.Authorization -match 'Bearer *') if (! $authorized) { throw 'Authorization header invalid' } $endpoint, $paramstring = $URI.Split('?') if ($paramstring) { $parameters = @{} foreach ($substring in $paramstring.split('&')) { $values = $substring.split('=') $parameters.Add( $values[0], $values[1] ) } } $query = $parameters['q'] $name = [regex]::matches($query, '(?<=track:")[^"]+').value $artist = [regex]::matches($query, '(?<=artist:")[^"]+').value $response = @{ Content = @{ tracks = @{ items = @(@{ id = [guid]::newguid() name = $name artists = @(@{name = $artist}) }) } } | ConvertTo-Json -Depth 5 -Compress } return $response } Export-ModuleMember -Function * |