Skip to content

Commit

Permalink
Update developer_setup.md (#23274)
Browse files Browse the repository at this point in the history
  • Loading branch information
chlowell authored Aug 10, 2024
1 parent e8defc7 commit 969cfcd
Showing 1 changed file with 38 additions and 58 deletions.
96 changes: 38 additions & 58 deletions documentation/developer_setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ use: "@autorest/[email protected]"
For the `use` section, the value should always be the latest version of the `@autorest/go` package.
The latest version can be found at the NPM [page][autorest_go] for `@autorest/go`.

For services that authenticate with Azure Active Directory, you **must** include the `security-scopes` parameter with the appropriate values (example below).
For services that authenticate with Microsoft Entra ID, you **must** include the `security-scopes` parameter with the appropriate values (example below).

```yaml
security-scopes: "https://vault.azure.net/.default"
Expand Down Expand Up @@ -160,30 +160,45 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/internal/recording"
"github.com/Azure/azure-sdk-for-go/sdk/internal/test/credential"
)
func createClientForRecording(t *testing.T, tableName string, serviceURL string, cred SharedKeyCredential) (*Client, error) {
func createClientForRecording(t *testing.T, tableName string, serviceURL string) (*Client, error) {
transport, err := recording.NewRecordingHTTPClient(t)
require.NoError(t, err)
options := &ClientOptions{
ClientOptions: azcore.ClientOptions{
Transport: client,
Transport: transport,
},
}
return NewClientWithSharedKey(runtime.JoinPaths(serviceURL, tableName), &cred, options)
// credential.New returns a credential for Entra ID authentication. It works in CI
// and local development, in all recording modes. To authenticate live tests on
// your machine, sign in to the Azure CLI or Azure Developer CLI.
cred, err := credential.New(nil)
require.NoError(t, err)
return NewClient(runtime.JoinPaths(serviceURL, tableName), &cred, options)
}
func startRecording(t *testing.T) {
err := recording.Start(t, recordingDirectory, nil)
require.NoError(t, err)
t.Cleanup(func() {
err := recording.Stop(t, nil)
require.NoError(t, err)
})
}
```

Including this in a file for test helper methods will ensure that before each test the developer simply has to add

```go
func TestExample(t *testing.T) {
err := recording.Start(t, "path/to/package", nil)
defer recording.Stop(t, nil)
startRecording(t)
client, err := createClientForRecording(t, "myTableName", "myServiceUrl", myCredential)
client, err := createClientForRecording(t, "myTableName", "myServiceUrl")
require.NoError(t, err)
...
<test code>
Expand All @@ -204,14 +219,10 @@ import (
"os"
"github.com/stretchr/testify/require"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/internal/recording"
)
const (
accountName := os.GetEnv("TABLES_PRIMARY_ACCOUNT_NAME")
accountKey := os.GetEnv("TABLES_PRIMARY_ACCOUNT_KEY")
)
const accountName = os.GetEnv("TABLES_PRIMARY_ACCOUNT_NAME")
// Test creating a single table
func TestCreateTable(t *testing.T) {
Expand All @@ -222,11 +233,8 @@ func TestCreateTable(t *testing.T) {
require.NoError(t, err)
}()
serviceUrl := fmt.Sprintf("https://%v.table.core.windows.net", accountName)
cred, err := azidentity.NewDefaultAzureCredential(nil)
require.NoError(t, err)
client, err := createClientForRecording(t, "tableName", serviceUrl, cred)
serviceURL := fmt.Sprintf("https://%v.table.core.windows.net", accountName)
client, err := createClientForRecording(t, "tableName", serviceURL)
require.NoError(t, err)
resp, err := client.Create()
Expand All @@ -239,7 +247,7 @@ func TestCreateTable(t *testing.T) {
}
```

The first part of the test above is for getting the secrets needed for authentication from your environment, best practice is to store your test secrets in environment variables.
The first part of the test above is for getting resource configuration from your environment.

The rest of the snippet shows a test that creates a single table and requirements (similar to assertions in other languages) that the response from the service has the same table name as the supplied parameter. Every test in Go has to have exactly one parameter, the `t *testing.T` object, and it must begin with `Test`. After making a service call or creating an object you can make assertions on that object by using the external `testify/require` library. In the example above, we "require" that the error returned is `nil`, meaning the call was successful and then we require that the response object has the same table name as supplied.

Expand All @@ -249,7 +257,7 @@ If you set the environment variable `AZURE_RECORD_MODE` to "record" and run `go

### Scrubbing Secrets

The recording files eventually live in the main repository (`github.com/Azure/azure-sdk-for-go`) and we need to make sure that all of these recordings are free from secrets. To do this we use Sanitizers with regular expressions for replacements. All of the available sanitizers are available as methods from the `recording` package. The recording methods generally take three parameters: the test instance (`t *testing.T`), the value to be removed (ie. an account name or key), and the value to use in replacement.
Recording files live in the assets repository (`github.com/Azure/azure-sdk-assets`) and must not contain secrets. We use sanitizers with regular expression replacements to prevent recording secrets. The test proxy has many built-in sanitizers enabled by default. However, you may need to add your own by calling functions from the `recording` package. These functions generally take three parameters: the test instance (`t *testing.T`), the value to be removed (ie. an account name or key), and the value to use in replacement.

| Sanitizer Type | Method |
| -------------- | ------ |
Expand All @@ -263,7 +271,9 @@ The recording files eventually live in the main repository (`github.com/Azure/az
| URI Sanitizer | `AddURISanitizer(value, regex string, options *RecordingOptions)` |
| URI Subscription ID Sanitizer | `AddURISubscriptionIDSanitizer(value string, options *RecordingOptions)` |

To add a scrubber that replaces the URL of your account use the `TestMain()` function to set sanitizers before you begin running tests.
You may also need to remove a built-in sanitizer overwriting a non-secret value needed by a test. To do this, call `RemoveRegisteredSanitizers` with a list of sanitizer IDs such as "AZSDK3430". See the [test proxy documentation](https://github.com/Azure/azure-sdk-tools/blob/main/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/SanitizerDictionary.cs) for a complete list of all the built-in sanitizers and their IDs.

Configure sanitizers before running tests:

```go
const recordingDirectory = "<path to service directory with assets.json file>/testdata"
Expand All @@ -290,15 +300,18 @@ func run(m *testing.M) int {
}()
}
// Set sanitizers in record mode
if recording.GetRecordMode() == "record" {
vaultUrl := os.Getenv("AZURE_KEYVAULT_URL")
err = recording.AddURISanitizer(fakeKvURL, vaultUrl, nil)
// sanitizers run in playback and recording modes
if recording.GetRecordMode() != recording.LiveMode {
// configure sanitizers here. For example:
err := recording.RemoveRegisteredSanitizers([]string{
"AZSDK3430", // body key $..id
}, nil)
if err != nil {
panic(err)
}
}
// run test cases
return m.Run()
}
```
Expand All @@ -319,47 +332,14 @@ func TestClient(t *testing.T) {
}
```

### Using `azidentity` Credentials In Tests

The credentials in `azidentity` are not automatically configured to run in playback mode. To make sure your tests run in playback mode even with `azidentity` credentials the best practice is to use a simple `FakeCredential` type that inserts a fake Authorization header to mock a credential. An example for swapping the `DefaultAzureCredential` using a helper function is shown below in the context of `aztables`

```go
type FakeCredential struct {}
func NewFakeCredential() *FakeCredential {
return &FakeCredential{}
}
func (f *FakeCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (*azcore.AccessToken, error) {
return &azcore.AccessToken{Token: "***", ExpiresOn: time.Now().Add(time.Hour)}, nil
}
func getAADCredential() (azcore.TokenCredential, error) {
if recording.GetRecordMode() == recording.PlaybackMode {
return NewFakeCredential(), nil
}
return azidentity.NewDefaultCredential(nil)
}
func TestClientWithAAD(t *testing.T) {
accountName := recording.GetEnvVariable(t, "TABLES_PRIMARY_ACCOUNT_NAME", "fakeAccountName")
cred, err := getAADCredential()
require.NoError(t, err)
...
...run tests...
}
```

The `FakeCredential` show here implements the `azcore.TokenCredential` interface and can be used anywhere the `azcore.TokenCredential` is used.

### Live Test Resource Management

If you have live tests that require Azure resources, you'll need to create a test resources config file for deployment during CI.
Please see the [test resource][test_resources] documentation for more info.

### Committing/Updating Recordings

The `assets.json` file located in your module directory is used by the Test Framework to figure out how to retrieve session records from the assets repo. In order to push new session records, you need to invoke:
Always inspect recordings for secrets before pushing them. The `assets.json` file located in your module directory is used by the Test Framework to figure out how to retrieve session records from the assets repo. In order to push new session records, you need to invoke:

```PowerShell
test-proxy push -a <path-to-assets.json>
Expand Down

0 comments on commit 969cfcd

Please sign in to comment.