How do I make a config change for an Azure Function during system tests, and reliably wait for the change to take effect?

Viewed 117

I have an Azure Function, and have a set of automated system tests for it that are written in TypeScript. We configure the Azure Function via the Azure portal i.e. Configuration > Application settings. (This is automated via Terraform.)

The Azure Function works fine, and the tests work fine until a test needs to change the configuration away from what was already set up. Despite my best efforts (see below), the config change appears successful, but the Azure Function runs using the previous config and so the test fails. I know the previous config is used because the output of the Azure Function includes the values it has read from the config.

I would like to be able to change the config during the test run, and have this change work reliably.

I'm using the node module azure/arm-appservice, specifically:

  • listApplicationSettings / updateApplicationSettings to make the change
  • restart (with softRestart = false and synchronous = true) to try to force the Azure Function to restart with the new config

I tried using syncFunctionTriggers but this has a bug - the node module expects a 204 return code, but the API actually returns 200 at least some of the time. I implemented my own version of this, but got the same kind of behaviour as with using the restart method - the call appears to succeed, but the next invocation of the Azure Function uses the old config.

This is the code that is the closest to the arm-appservice node module:

export class AzureResourceManager {
    client: WebSiteManagementClient
  
    constructor(client: WebSiteManagementClient) {
      this.client = client
    }
  
    static async create(subscriptionId: string) {
      credentials = await AzureCliCredentials.create()
      const client = new WebSiteManagementClient(credentials, subscriptionId)
  
      return new AzureResourceManager(client)
    }
    
    updateApplicationSetting = async (
      resourceName: string,
      onUpdate: (
        current: Record<string, string>
      ) => false | Record<string, string>
    ) => {
      const allResources = await this.client.webApps.list()
      const resource = allResources.find(s => s.name === resourceName)
      if (!resource) {
        throw `Resource '${resourceName}' could name be found in subscription '${this.client.subscriptionId}'`
      }

      const appSettings = await this.client.webApps.listApplicationSettings(
        resource.resourceGroup!,
        resource.name!
      )

      const updatedProperties = onUpdate(appSettings.properties ?? {})
      if (updatedProperties === false) {
        return
      }

      await this.client.webApps.updateApplicationSettings(
        resource.resourceGroup!,
        resource.name!,
        {
          ...appSettings,
          properties: {
            ...updatedProperties
          }
        }
      )

      await this.client.webApps.restart(
        resource.resourceGroup!,
        resource.name!,
        { softRestart: false, synchronous: true }
      )
    }
  }

And it is called by this code, to set 2 parameters:

export class AzureEnvironmentConfigurator {
  setConfig = async (
    param1Value: number,
    param2Value: number
  ): Promise<void> => {

    const arm = await AzureResourceManager.create(SUBSCRIPTION_ID)

    await arm.updateApplicationSetting(
      RESOURCE_NAME,
      settings => {
        if (
          param1Value === settings[PARAM1_KEY] &&
          param2Value === settings[PARAM2_KEY]
        ) {
          return false
        }

        return {
          ...settings,
          [PARAM1_KEY]: param1Value,
          [PARAM2_KEY]: param2Value
        }
      }
    )
  }
}
0 Answers
Related