diff --git a/README.md b/README.md
index 29fe86f..35fa1f3 100644
--- a/README.md
+++ b/README.md
@@ -56,6 +56,26 @@ The following NuGet packages have been published:
| [CommunityToolkit.Datasync.Server.NSwag] | ![NSwag Library Version][vs-nswag] | ![NSwag Library Downloads][ds-nswag] |
| [CommunityToolkit.Datasync.Server.Swashbuckle] | ![Swashbuckle Library Version][vs-swashbuckle] | ![Swashbuckle Library Downloads][ds-swashbuckle] |
+## Running Live Tests
+
+The test suite for the library includes "live tests" against real servers that are not normally run. To run those tests, you will need access to an
+Azure account (you can sign up for one for free):
+
+1. Install the [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd)
+2. Run `azd up` in a command line.
+
+This script will create several resources. The cost of running those resources is approximately $40/month (US dollars). However, you will only have
+to run the services for less than an hour, so the cost of testing the library should be minimal. The process will create a `.runsettings` file in the
+tests directory which you can use to enable the live testing.
+
+Live testing can be run using the Visual Studio Test Explorer or via `dotnet test`.
+
+Once you have completed running the tests, you can remove the created services using `azd down`. This will also remove the `.runsettings` file so that
+live tests are not attempted any more.
+
+> **NOTE**: The `.runsettings` file contains secrets. It should not be checked in. We have added this file to the `.gitignore` to ensure that it is
+> not checked into public GitHub repositories.
+
## 🌍 Roadmap
Read what we [plan for next iterations](https://github.com/CommunityToolkit/Datasync/milestones), and feel free to ask questions.
diff --git a/azure.yaml b/azure.yaml
new file mode 100644
index 0000000..9a17799
--- /dev/null
+++ b/azure.yaml
@@ -0,0 +1,37 @@
+# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
+
+##
+## This AZD template will create all the resources necessary to
+## test the Community Toolkit/Datasync libraries, plus deploy an
+## app service (on free tier) that talks to the AzSQL service
+## for the TODO Sample Service
+##
+
+name: communitytoolkit-datasync-test-services
+
+hooks:
+ postprovision:
+ posix:
+ interactive: true
+ shell: sh
+ run: ./infra/scripts/write-runsettings.sh
+ windows:
+ interactive: true
+ shell: pwsh
+ run: ./infra/scripts/write-runsettings.ps1
+
+ predown:
+ posix:
+ interactive: true
+ shell: sh
+ run: ./infra/scripts/remove-runsettings.sh
+ windows:
+ interactive: true
+ shell: pwsh
+ run: ./infra/scripts/remove-runsettings.ps1
+
+services:
+ todoservice:
+ language: csharp
+ project: ./samples/datasync-server/src/Sample.Datasync.Server
+ host: appservice
diff --git a/infra/main.bicep b/infra/main.bicep
new file mode 100644
index 0000000..a5ce550
--- /dev/null
+++ b/infra/main.bicep
@@ -0,0 +1,54 @@
+targetScope = 'subscription'
+
+@minLength(1)
+@maxLength(64)
+@description('Name of the the environment which is used to generate a short unique hash used in all resources.')
+param environmentName string
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string
+
+@description('Id of the user or app to assign application roles')
+param principalId string = ''
+
+@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
+param sqlAdminUsername string = 'appadmin'
+
+@secure()
+@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
+param sqlAdminPassword string = newGuid()
+
+/*********************************************************************************/
+
+var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
+var tags = { 'azd-env-name': environmentName }
+
+/*********************************************************************************/
+
+resource rg 'Microsoft.Resources/resourceGroups@2024-07-01' = {
+ name: 'rg-${environmentName}'
+ location: location
+ tags: tags
+}
+
+module resources './resources.bicep' = {
+ name: 'resources'
+ scope: rg
+ params: {
+ location: location
+ tags: tags
+ principalId: principalId
+ resourceToken: resourceToken
+ serviceName: 'todoservice'
+ sqlAdminUsername: sqlAdminUsername
+ sqlAdminPassword: sqlAdminPassword
+ }
+}
+
+/*********************************************************************************/
+
+output AZSQL_CONNECTION_STRING string = resources.outputs.AZSQL_CONNECTIONSTRING
+output PGSQL_CONNECTION_STRING string = resources.outputs.PGSQL_CONNECTIONSTRING
+output COSMOS_CONNECTION_STRING string = resources.outputs.COSMOS_CONNECTIONSTRING
+output SERVICE_ENDPOINT string = resources.outputs.SERVICE_ENDPOINT
diff --git a/infra/main.parameters.json b/infra/main.parameters.json
new file mode 100644
index 0000000..7d7d8f9
--- /dev/null
+++ b/infra/main.parameters.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "environmentName": {
+ "value": "${AZURE_ENV_NAME}"
+ },
+ "location": {
+ "value": "${AZURE_LOCATION}"
+ },
+ "principalId": {
+ "value": "${AZURE_PRINCIPAL_ID}"
+ },
+ "sqlAdminUsername": {
+ "value": "appadmin"
+ },
+ "sqlAdminPassword": {
+ "value": "$(secretOrRandomPassword)"
+ }
+ }
+}
\ No newline at end of file
diff --git a/infra/modules/appservice.bicep b/infra/modules/appservice.bicep
new file mode 100644
index 0000000..d899c53
--- /dev/null
+++ b/infra/modules/appservice.bicep
@@ -0,0 +1,104 @@
+targetScope = 'resourceGroup'
+
+@minLength(1)
+@description('The name of the App Service Plan resource')
+param appServicePlanName string
+
+@minLength(1)
+@description('The name of the App Service resource')
+param appServiceName string
+
+@minLength(1)
+@description('The name of the test database to create')
+param databaseName string = 'tododb'
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string = resourceGroup().location
+
+@description('The name of the deployment in azure.yaml')
+param serviceName string = 'todoservice'
+
+@description('The name of the SQL Server to create.')
+param sqlServerName string
+
+@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
+param sqlAdminUsername string = 'appadmin'
+
+@secure()
+@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
+param sqlAdminPassword string = newGuid()
+
+@description('The list of tags to apply to all resources.')
+param tags object = {}
+
+/*********************************************************************************/
+
+resource azsql_server 'Microsoft.Sql/servers@2024-05-01-preview' existing = {
+ name: sqlServerName
+}
+
+resource sqldb 'Microsoft.Sql/servers/databases@2024-05-01-preview' = {
+ name: databaseName
+ parent: azsql_server
+ location: location
+ tags: tags
+ sku: {
+ name: 'Basic'
+ }
+ properties: {
+ collation: 'SQL_Latin1_General_CP1_CI_AS'
+ maxSizeBytes: 1073741824
+ }
+}
+
+resource appsvc_plan 'Microsoft.Web/serverfarms@2024-04-01' = {
+ name: appServicePlanName
+ location: location
+ tags: tags
+ sku: {
+ name: 'B1'
+ capacity: 1
+ }
+}
+
+resource app_service 'Microsoft.Web/sites@2024-04-01' = {
+ name: appServiceName
+ location: location
+ tags: union(tags, {
+ 'azd-service-name': serviceName
+ 'hidden-related:${appsvc_plan.id}': 'empty'
+ })
+ properties: {
+ httpsOnly: true
+ serverFarmId: appsvc_plan.id
+ siteConfig: {
+ ftpsState: 'Disabled'
+ minTlsVersion: '1.2'
+ }
+ }
+
+ resource configLogs 'config' = {
+ name: 'logs'
+ properties: {
+ applicationLogs: { fileSystem: { level: 'Verbose' } }
+ detailedErrorMessages: { enabled: true }
+ failedRequestsTracing: { enabled: true }
+ httpLogs: { fileSystem: { retentionInMb: 35, retentionInDays: 3, enabled: true } }
+ }
+ }
+
+ resource connectionStrings 'config' = {
+ name: 'connectionstrings'
+ properties: {
+ DefaultConnection: {
+ value: 'Data Source=tcp:${azsql_server.properties.fullyQualifiedDomainName},1433;Initial Catalog=${sqldb.name};User Id=${sqlAdminUsername};Password=${sqlAdminPassword};'
+ type: 'SQLAzure'
+ }
+ }
+ }
+}
+
+/*********************************************************************************/
+
+output SERVICE_ENDPOINT string = 'https://${app_service.properties.defaultHostName}'
diff --git a/infra/modules/azuresql.bicep b/infra/modules/azuresql.bicep
new file mode 100644
index 0000000..e145ab1
--- /dev/null
+++ b/infra/modules/azuresql.bicep
@@ -0,0 +1,78 @@
+targetScope = 'resourceGroup'
+
+@description('The list of firewall rules to install')
+param firewallRules FirewallRule[] = [
+ { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
+]
+
+@minLength(1)
+@description('The name of the test database to create')
+param databaseName string = 'unittests'
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string = resourceGroup().location
+
+@description('The name of the SQL Server to create.')
+param sqlServerName string
+
+@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
+param sqlAdminUsername string = 'appadmin'
+
+@secure()
+@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
+param sqlAdminPassword string = newGuid()
+
+@description('The list of tags to apply to all resources.')
+param tags object = {}
+
+/*********************************************************************************/
+
+resource azsql_server 'Microsoft.Sql/servers@2024-05-01-preview' = {
+ name: sqlServerName
+ location: location
+ tags: tags
+ properties: {
+ version: '12.0'
+ minimalTlsVersion: '1.2'
+ publicNetworkAccess: 'Enabled'
+ administratorLogin: sqlAdminUsername
+ administratorLoginPassword: sqlAdminPassword
+ }
+
+ resource fw 'firewallRules' = [
+ for fwRule in firewallRules: {
+ name: '${fwRule.startIpAddress}-${fwRule.endIpAddress}'
+ properties: {
+ startIpAddress: fwRule.startIpAddress
+ endIpAddress: fwRule.endIpAddress
+ }
+ }
+ ]
+}
+
+resource azsql_database 'Microsoft.Sql/servers/databases@2024-05-01-preview' = {
+ name: databaseName
+ parent: azsql_server
+ location: location
+ tags: tags
+ sku: {
+ name: 'Basic'
+ tier: 'Basic'
+ }
+ properties: {
+ collation: 'SQL_Latin1_General_CP1_CI_AS'
+ }
+}
+
+/*********************************************************************************/
+
+#disable-next-line outputs-should-not-contain-secrets
+output AZSQL_CONNECTIONSTRING string = 'Data Source=tcp:${azsql_server.properties.fullyQualifiedDomainName},1433;Initial Catalog=${azsql_database.name};User Id=${azsql_server.properties.administratorLogin}@${azsql_server.properties.fullyQualifiedDomainName};Password=${sqlAdminPassword};Encrypt=True;TrustServerCertificate=False'
+
+/*********************************************************************************/
+
+type FirewallRule = {
+ startIpAddress: string
+ endIpAddress: string
+}
diff --git a/infra/modules/cosmos.bicep b/infra/modules/cosmos.bicep
new file mode 100644
index 0000000..a4704da
--- /dev/null
+++ b/infra/modules/cosmos.bicep
@@ -0,0 +1,91 @@
+targetScope = 'resourceGroup'
+
+@minLength(1)
+@description('The name of the test container to create')
+param containerName string = 'Movies'
+
+@minLength(1)
+@description('The name of the test database to create')
+param databaseName string = 'unittests'
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string = resourceGroup().location
+
+@description('The name of the SQL Server to create.')
+param serverName string
+
+@description('The list of tags to apply to all resources.')
+param tags object = {}
+
+/*********************************************************************************/
+
+resource cosmos_account 'Microsoft.DocumentDB/databaseAccounts@2024-02-15-preview' = {
+ name: serverName
+ location: location
+ tags: tags
+ kind: 'GlobalDocumentDB'
+ properties: {
+ consistencyPolicy: {
+ defaultConsistencyLevel: 'Session'
+ }
+ locations: [
+ {
+ locationName: location
+ failoverPriority: 0
+ isZoneRedundant: false
+ }
+ ]
+ databaseAccountOfferType: 'Standard'
+ enableAutomaticFailover: false
+ disableKeyBasedMetadataWriteAccess: true
+ }
+}
+
+resource cosmos_database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-02-15-preview' = {
+ name: databaseName
+ parent: cosmos_account
+ properties: {
+ resource: {
+ id: databaseName
+ }
+ }
+}
+
+resource cosmos_container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-09-15' = {
+ name: containerName
+ parent: cosmos_database
+ properties: {
+ resource: {
+ id: containerName
+ partitionKey: {
+ paths: ['/id']
+ kind: 'Hash'
+ }
+ indexingPolicy: {
+ indexingMode: 'consistent'
+ includedPaths: [
+ { path: '/*' }
+ ]
+ excludedPaths: [
+ { path: '/_etag/?' }
+ ]
+ compositeIndexes: [
+ [
+ { path: '/UpdatedAt', order: 'ascending' }
+ { path: '/Id', order: 'ascending' }
+ ]
+ ]
+ }
+ defaultTtl: 86400
+ }
+ options: {
+ throughput: 400
+ }
+ }
+}
+
+/*********************************************************************************/
+
+#disable-next-line outputs-should-not-contain-secrets
+output COSMOS_CONNECTIONSTRING string = cosmos_account.listConnectionStrings().connectionStrings[0].connectionString
diff --git a/infra/modules/postgresql.bicep b/infra/modules/postgresql.bicep
new file mode 100644
index 0000000..eb1ab6a
--- /dev/null
+++ b/infra/modules/postgresql.bicep
@@ -0,0 +1,89 @@
+targetScope = 'resourceGroup'
+
+@description('The list of firewall rules to install')
+param firewallRules FirewallRule[] = [
+ { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
+]
+
+@minLength(1)
+@description('The name of the test database to create')
+param databaseName string = 'unittests'
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string = resourceGroup().location
+
+@description('The name of the SQL Server to create.')
+param sqlServerName string
+
+@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
+param sqlAdminUsername string = 'appadmin'
+
+@secure()
+@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
+param sqlAdminPassword string = newGuid()
+
+@description('The list of tags to apply to all resources.')
+param tags object = {}
+
+/*********************************************************************************/
+
+resource pgsql_server 'Microsoft.DBforPostgreSQL/flexibleServers@2023-06-01-preview' = {
+ name: sqlServerName
+ location: location
+ tags: tags
+ sku: {
+ name: 'Standard_B1ms'
+ tier: 'Burstable'
+ }
+ properties: {
+ administratorLogin: sqlAdminUsername
+ administratorLoginPassword: sqlAdminPassword
+ createMode: 'Default'
+ authConfig: {
+ activeDirectoryAuth: 'Disabled'
+ passwordAuth: 'Enabled'
+ }
+ backup: {
+ backupRetentionDays: 7
+ geoRedundantBackup: 'Disabled'
+ }
+ highAvailability: {
+ mode: 'Disabled'
+ }
+ storage: {
+ storageSizeGB: 32
+ autoGrow: 'Disabled'
+ }
+ version: '15'
+ }
+
+ resource fw 'firewallRules' = [ for (fwRule, idx) in firewallRules : {
+ name: 'fw${idx}'
+ properties: {
+ startIpAddress: fwRule.startIpAddress
+ endIpAddress: fwRule.endIpAddress
+ }
+ }]
+}
+
+resource pgsql_database 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-03-01-preview' = {
+ name: databaseName
+ parent: pgsql_server
+ properties: {
+ charset: 'UTF8'
+ collation: 'en_US.utf8'
+ }
+}
+
+/*********************************************************************************/
+
+#disable-next-line outputs-should-not-contain-secrets
+output PGSQL_CONNECTIONSTRING string = 'Host=${pgsql_server.properties.fullyQualifiedDomainName};Database=${pgsql_database.name};Username=${pgsql_server.properties.administratorLogin};Password=${sqlAdminPassword}'
+
+/*********************************************************************************/
+
+type FirewallRule = {
+ startIpAddress: string
+ endIpAddress: string
+}
diff --git a/infra/resources.bicep b/infra/resources.bicep
new file mode 100644
index 0000000..ce72f1d
--- /dev/null
+++ b/infra/resources.bicep
@@ -0,0 +1,114 @@
+targetScope = 'resourceGroup'
+
+@minLength(1)
+@description('Primary location for all resources')
+param location string
+
+@description('The IP address of the place running the tests')
+param clientIpAddress string?
+
+@description('Id of the user or app to assign application roles')
+#disable-next-line no-unused-params
+param principalId string = ''
+
+@description('The resource token to use in constructing all service names')
+param resourceToken string
+
+@description('The service name (in azure.yaml) to use for code deployment')
+param serviceName string = 'todoservice'
+
+@description('Optional - the SQL Server administrator password. If not provided, the username will be \'appadmin\'.')
+param sqlAdminUsername string = 'appadmin'
+
+@secure()
+@description('Optional - SQL Server administrator password. If not provided, a random password will be generated.')
+param sqlAdminPassword string = newGuid()
+
+@description('The list of tags to apply to all resources.')
+param tags object = {}
+
+/*********************************************************************************/
+
+var appServicePlanName = 'asp-${resourceToken}'
+var appServiceName = 'web-${resourceToken}'
+var azsqlServerName = 'sql-${resourceToken}'
+var cosmosServerName = 'cosmos-${resourceToken}'
+var pgsqlServerName = 'pgsql-${resourceToken}'
+
+var testDatabaseName = 'unittests'
+var cosmosContainerName = 'Movies'
+
+var clientIpFirewallRules = clientIpAddress != null
+ ? [
+ { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
+ {
+ endIpAddress: parseCidr('${clientIpAddress!}/32').lastUsable
+ startIpAddress: parseCidr('${clientIpAddress!}/32').firstUsable
+ }
+ ]
+ : [
+ { endIpAddress: '255.255.255.255', startIpAddress: '0.0.0.0' }
+ ]
+
+/*********************************************************************************/
+
+module azuresql './modules/azuresql.bicep' = {
+ name: 'azsql-deployment-${resourceToken}'
+ params: {
+ location: location
+ tags: tags
+ databaseName: testDatabaseName
+ firewallRules: clientIpFirewallRules
+ sqlServerName: azsqlServerName
+ sqlAdminUsername: sqlAdminUsername
+ sqlAdminPassword: sqlAdminPassword
+ }
+}
+
+module pgsql './modules/postgresql.bicep' = {
+ name: 'pgsql-deployment-${resourceToken}'
+ params: {
+ location: location
+ tags: tags
+ databaseName: testDatabaseName
+ firewallRules: clientIpFirewallRules
+ sqlServerName: pgsqlServerName
+ sqlAdminUsername: sqlAdminUsername
+ sqlAdminPassword: sqlAdminPassword
+ }
+}
+
+module cosmos './modules/cosmos.bicep' = {
+ name: 'cosmos-deployment-${resourceToken}'
+ params: {
+ location: location
+ tags: tags
+ databaseName: testDatabaseName
+ containerName: cosmosContainerName
+ serverName: cosmosServerName
+ }
+}
+
+module app_service './modules/appservice.bicep' = {
+ name: 'appsvc-deployment-${resourceToken}'
+ params: {
+ location: location
+ tags: tags
+ serviceName: serviceName
+ sqlServerName: azsqlServerName
+ appServicePlanName: appServicePlanName
+ appServiceName: appServiceName
+ sqlAdminUsername: sqlAdminUsername
+ sqlAdminPassword: sqlAdminPassword
+ }
+ dependsOn: [
+ azuresql
+ ]
+}
+
+/*********************************************************************************/
+
+output AZSQL_CONNECTIONSTRING string = azuresql.outputs.AZSQL_CONNECTIONSTRING
+output PGSQL_CONNECTIONSTRING string = pgsql.outputs.PGSQL_CONNECTIONSTRING
+output COSMOS_CONNECTIONSTRING string = cosmos.outputs.COSMOS_CONNECTIONSTRING
+output SERVICE_ENDPOINT string = app_service.outputs.SERVICE_ENDPOINT
diff --git a/infra/scripts/remove-runsettings.ps1 b/infra/scripts/remove-runsettings.ps1
new file mode 100644
index 0000000..1544150
--- /dev/null
+++ b/infra/scripts/remove-runsettings.ps1
@@ -0,0 +1,12 @@
+<#
+.SYNOPSIS
+ This scripts writes a suitable runsettings file for the tests so that
+ the tests use the appropriate services for live testing.
+#>
+$outputFile = "tests\.runsettings"
+
+if (Test-Path $outputFile) {
+ Remove-Item -Path $outputFile
+} else {
+ Write-Output "File $($outputFile) does not exist."
+}
diff --git a/infra/scripts/write-runsettings.ps1 b/infra/scripts/write-runsettings.ps1
new file mode 100644
index 0000000..a10aa6a
--- /dev/null
+++ b/infra/scripts/write-runsettings.ps1
@@ -0,0 +1,23 @@
+<#
+.SYNOPSIS
+ This scripts writes a suitable runsettings file for the tests so that
+ the tests use the appropriate services for live testing.
+#>
+$outputs = (azd env get-values --output json | ConvertFrom-Json)
+$outputFile = "tests\.runsettings"
+
+$fileContents = @"
+
+
+
+
+ $($outputs.AZSQL_CONNECTION_STRING)
+ $($outputs.COSMOS_CONNECTION_STRING)
+ $($outputs.PGSQL_CONNECTION_STRING)
+ true
+
+
+
+"@
+
+$fileContents | Out-File -FilePath $outputFile
diff --git a/tests/infra/README.md b/tests/infra/README.md
deleted file mode 100644
index 0acf6d6..0000000
--- a/tests/infra/README.md
+++ /dev/null
@@ -1,90 +0,0 @@
-# Test Infrastructure
-
-Some of the tests in this repository rely on Azure Infrastructure so that they can run against a real
-database service. The files within this folder are the 'Infrastructure as Code' necessary to create
-the database services that are required and output the appropriate connection strings.
-
-## Deployment Instructions
-
-First, login to Azure with the Azure CLI and set a subscription:
-
-```bash
-$ az login
-$ az account set --subscription
-```
-
-Then create a resource group with a good name and location; with bash:
-
-```bash
-$ export RG=datasync-testing
-$ az group create -l westus3 -n $RG
-$ az deployment group create -n "d-$RG" -g $RG --template-file ./infra/main.bicep
-```
-
-Or, with PowerShell:
-
-```powershell
-> $env:RG="datasync-testing"
-> az group create -l westus3 -n $env:RG
-> az deployment group create -n "d-$($env:RG)" -g $env:RG --template-file .\infra\main.bicep
-```
-
-Replace the definition of `RG` with a unique name. This will ensure all the resources are unique
-and that your tests will run to completion properly. It takes approximately 15-20 minutes to provision
-the resources. The following resources are created:
-
-* Azure SQL Server and Database (Basic SKU - $4.90 per month)
-* Azure Cosmos Database (Standard SKU - $0.88 per month)
-* Azure DB for PostgreSQL flexible server (Burstable B1ms SKU - $12.99 per month)
-
-We recommend spinning up the databases for testing as needed, then removing them again. You only need to
-run live tests when changing the repository code.
-
-## Running live tests
-
-The deployment returns some output which you can read as follows; with bash:
-
-```bash
-$ az deployment group show -n "d-$RG" -g $RG --query properties.outputs
-```
-
-Or with PowerShell:
-
-```powershell
-> az deployment group show -n "d-$($env:RG)" -g $env:RG --query properties.outputs
-```
-
-Create a `.runsettings` file in the `tests` directory (or the top level repository directory):
-
-```xml
-
-
-
-
- {{connection string}}
- {{connection string}}
- {{connection string}}
-
-
-
-```
-
-Replace the connection strings with the appropriate value from the deployment outputs.
-
-> You can also turn on logging by setting the `ENABLE_SQL_LOGGING` environment variable to `true`.
-
-You can either run the tests from the Visual Studio Test Explorer or run `dotnet test`.
-
-## Shutting down the test resources
-
-Delete the resource group containing the resources; with bash:
-
-```bash
-$ az group delete -n $RG
-```
-
-Or with PowerShell:
-
-```powershell
-> az group delete -n $env:RG
-```
diff --git a/tests/infra/databases/azure-sql.bicep b/tests/infra/databases/azure-sql.bicep
deleted file mode 100644
index 7aa7406..0000000
--- a/tests/infra/databases/azure-sql.bicep
+++ /dev/null
@@ -1,60 +0,0 @@
-targetScope = 'resourceGroup'
-
-@description('The IP address of the place running the tests')
-param clientIpAddress string?
-
-@description('The name of the database to create')
-param databaseName string = 'unittests'
-
-@minLength(1)
-@description('Primary location for all resources')
-param location string
-
-@description('The administrator username for the databases')
-param administratorUsername string
-
-@secure()
-@description('The administrator password for the databases')
-param administratorPassword string
-
-var resourceToken = toLower(uniqueString(subscription().id, resourceGroup().name, location))
-
-var clientIpFirewallRules = clientIpAddress != null ? [
- { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
- { endIpAddress: parseCidr('${clientIpAddress!}/32').lastUsable, startIpAddress: parseCidr('${clientIpAddress!}/32').firstUsable }
-] : [
- { endIpAddress: '255.255.255.255', startIpAddress: '0.0.0.0' }
-]
-
-resource server 'Microsoft.Sql/servers@2023-08-01-preview' = {
- name: 'azsql-${resourceToken}'
- location: location
- properties: {
- administratorLogin: administratorUsername
- administratorLoginPassword: administratorPassword
- }
-
- resource fw 'firewallRules' = [ for fwRule in clientIpFirewallRules : {
- name: '${fwRule.startIpAddress}-${fwRule.endIpAddress}'
- properties: {
- startIpAddress: fwRule.startIpAddress
- endIpAddress: fwRule.endIpAddress
- }
- }]
-}
-
-resource database 'Microsoft.Sql/servers/databases@2023-08-01-preview' = {
- name: databaseName
- parent: server
- location: location
- sku: {
- name: 'Basic'
- tier: 'Basic'
- }
- properties: {
- collation: 'SQL_Latin1_General_CP1_CI_AS'
- }
-}
-
-#disable-next-line outputs-should-not-contain-secrets
-output connectionString string = 'Data Source=tcp:${server.properties.fullyQualifiedDomainName},1433;Initial Catalog=${database.name};User Id=${administratorUsername}@${server.properties.fullyQualifiedDomainName};Password=${administratorPassword};Encrypt=True;TrustServerCertificate=False'
diff --git a/tests/infra/databases/cosmos.bicep b/tests/infra/databases/cosmos.bicep
deleted file mode 100644
index c244d0b..0000000
--- a/tests/infra/databases/cosmos.bicep
+++ /dev/null
@@ -1,92 +0,0 @@
-targetScope = 'resourceGroup'
-
-@description('The IP address of the place running the tests')
-#disable-next-line no-unused-params
-param clientIpAddress string?
-
-@description('The name of the database to create')
-param databaseName string = 'unittests'
-
-@minLength(1)
-@description('Primary location for all resources')
-param location string
-
-@description('The administrator username for the database')
-#disable-next-line no-unused-params
-param administratorUsername string?
-
-@secure()
-@description('The administrator password for the database')
-#disable-next-line no-unused-params
-param administratorPassword string?
-
-var containerName = 'Movies'
-var resourceToken = toLower(uniqueString(subscription().id, resourceGroup().name, location))
-
-resource account 'Microsoft.DocumentDB/databaseAccounts@2024-02-15-preview' = {
- name: 'cosmos-${resourceToken}'
- location: location
- kind: 'GlobalDocumentDB'
- properties: {
- consistencyPolicy: {
- defaultConsistencyLevel: 'Session'
- }
- locations: [
- {
- locationName: location
- failoverPriority: 0
- isZoneRedundant: false
- }
- ]
- databaseAccountOfferType: 'Standard'
- enableAutomaticFailover: false
- disableKeyBasedMetadataWriteAccess: true
- }
-}
-
-resource database 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2024-02-15-preview' = {
- name: databaseName
- parent: account
- properties: {
- resource: {
- id: databaseName
- }
- }
-}
-
-resource container 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-09-15' = {
- name: containerName
- parent: database
- properties: {
- resource: {
- id: containerName
- partitionKey: {
- paths: [ '/id' ]
- kind: 'Hash'
- }
- indexingPolicy: {
- indexingMode: 'consistent'
- includedPaths: [
- { path: '/*' }
- ]
- excludedPaths: [
- { path: '/_etag/?' }
- ]
- compositeIndexes: [
- [
- { path: '/UpdatedAt', order: 'ascending' }
- { path: '/Id', order: 'ascending' }
- ]
- ]
- }
- defaultTtl: 86400
- }
- options: {
- throughput: 400
- }
- }
-}
-
-
-#disable-next-line outputs-should-not-contain-secrets
-output connectionString string = account.listConnectionStrings().connectionStrings[0].connectionString
diff --git a/tests/infra/databases/postgresql.bicep b/tests/infra/databases/postgresql.bicep
deleted file mode 100644
index 9fddf28..0000000
--- a/tests/infra/databases/postgresql.bicep
+++ /dev/null
@@ -1,77 +0,0 @@
-targetScope = 'resourceGroup'
-
-@description('The IP address of the place running the tests')
-param clientIpAddress string?
-
-@description('The name of the database to create')
-param databaseName string = 'unittests'
-
-@minLength(1)
-@description('Primary location for all resources')
-param location string
-
-@description('The administrator username for the database')
-param administratorUsername string
-
-@secure()
-@description('The administrator password for the database')
-param administratorPassword string
-
-var resourceToken = toLower(uniqueString(subscription().id, resourceGroup().name, location))
-var clientIpFirewallRules = clientIpAddress != null ? [
- { startIpAddress: '0.0.0.0', endIpAddress: '0.0.0.0' }
- { endIpAddress: parseCidr('${clientIpAddress!}/32').lastUsable, startIpAddress: parseCidr('${clientIpAddress!}/32').firstUsable }
-] : [
- { endIpAddress: '255.255.255.255', startIpAddress: '0.0.0.0' }
-]
-
-
-resource server 'Microsoft.DBforPostgreSQL/flexibleServers@2023-06-01-preview' = {
- name: 'pgserver-${resourceToken}'
- location: location
- sku: {
- name: 'Standard_B1ms'
- tier: 'Burstable'
- }
- properties: {
- administratorLogin: administratorUsername
- administratorLoginPassword: administratorPassword
- createMode: 'Default'
- authConfig: {
- activeDirectoryAuth: 'Disabled'
- passwordAuth: 'Enabled'
- }
- backup: {
- backupRetentionDays: 7
- geoRedundantBackup: 'Disabled'
- }
- highAvailability: {
- mode: 'Disabled'
- }
- storage: {
- storageSizeGB: 32
- autoGrow: 'Disabled'
- }
- version: '15'
- }
-
- resource fw 'firewallRules' = [ for (fwRule, idx) in clientIpFirewallRules : {
- name: 'fw${idx}'
- properties: {
- startIpAddress: fwRule.startIpAddress
- endIpAddress: fwRule.endIpAddress
- }
- }]
-}
-
-resource database 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2023-03-01-preview' = {
- name: databaseName
- parent: server
- properties: {
- charset: 'UTF8'
- collation: 'en_US.utf8'
- }
-}
-
-#disable-next-line outputs-should-not-contain-secrets
-output connectionString string = 'Host=${server.properties.fullyQualifiedDomainName};Database=${database.name};Username=${administratorUsername};Password=${administratorPassword}'
diff --git a/tests/infra/main.bicep b/tests/infra/main.bicep
deleted file mode 100644
index a95537c..0000000
--- a/tests/infra/main.bicep
+++ /dev/null
@@ -1,59 +0,0 @@
-targetScope = 'resourceGroup'
-
-@description('The IP address of the place running the tests')
-param clientIpAddress string?
-
-@minLength(1)
-@description('Primary location for all resources')
-param location string = resourceGroup().location
-
-@description('The administrator username for the databases')
-param administratorUsername string = 'testadmin'
-
-@secure()
-@description('The administrator password for the databases')
-param administratorPassword string = newGuid()
-
-var resourceToken = toLower(uniqueString(subscription().id, resourceGroup().name, location))
-
-// Azure SQL
-module azuresql './databases/azure-sql.bicep' = {
- name: 'deploy-azuresql-${resourceToken}'
- params: {
- administratorUsername: administratorUsername
- administratorPassword: administratorPassword
- clientIpAddress: clientIpAddress
- location: location
- }
-}
-
-#disable-next-line outputs-should-not-contain-secrets
-output AZSQL_CONNECTIONSTRING string = azuresql.outputs.connectionString
-
-// PostgreSQL
-module postgresql './databases/postgresql.bicep' = {
- name: 'deploy-postgresql-${resourceToken}'
- params: {
- administratorUsername: administratorUsername
- administratorPassword: administratorPassword
- clientIpAddress: clientIpAddress
- location: location
- }
-}
-
-#disable-next-line outputs-should-not-contain-secrets
-output PGSQL_CONNECTIONSTRING string = postgresql.outputs.connectionString
-
-// Cosmos
-module cosmos './databases/cosmos.bicep' = {
- name: 'deploy-cosmos-${resourceToken}'
- params: {
- administratorUsername: administratorUsername
- administratorPassword: administratorPassword
- clientIpAddress: clientIpAddress
- location: location
- }
-}
-
-#disable-next-line outputs-should-not-contain-secrets
-output COSMOS_CONNECTIONSTRING string = cosmos.outputs.connectionString