TypeError: Cannot read properties of undefined (reading 'byteLength')　AWS SDK for JavaScript v3で少しハマった話

## はじめに
ローカルではAWS CLIのconfig設定（[設定の基本](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html)を参照）をしていたので問題にならなかったが、CIでテスト実行しようとした時に、SDKのクライアントのコンストラクタでクレデンシャルを設定しようとしてハマったので、気を付けたいポイントについて備忘録を残す。

※以下ではCI上でSDKのクライアントのインスタンスをmock化しないで利用しているが、CI環境でテストするのにそもそもSDKをそのまま動かしているのはおかしいのでは…？という突っ込みが当然あると思う。結合でテストしたい場面ではないのであれば、単体テストとして実装するので本来はmockにすべきだが、今回は敢えてmockにしないとどういう事が起きるのか？mockにしないでテスト＝単体テストではなく結合テストをCI環境で簡単に実行できるのか？を検証してみたかったので、モックではなく、SDKをそのまま使うという事をした。

## 結論　どうすればいいか？
以下のように、`accessKeyId/secretAccessKey`を指定する。
```js
new DynamoDBClient({
	// 省略
	credentials: { accessKeyId: 'dumy', secretAccessKey: 'dumy' }
});
```

※エラーが出たconfig設定は以下。

```js
new DynamoDBClient({
	// 省略
	credentials: { AccessKeyId: 'dumy', SecretAccessKey: 'dumy' }
});
```

以下で解決に至る経緯を見ていく。

## 経緯
### ①`CredentialsProviderError: Could not load credentials from any providers`というエラーになる
まず`CredentialsProviderError: Could not load credentials from any providers`というエラーが出た。

これは単純にローカルではAWS CLIを利用していた関係で[Node.js での認証情報の設定](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html)にあるように、`~/.aws/credentials`からクレデンシャルを読み込んで動いていたが、CIではそもそもその設定がないのでクレデンシャルがなく、エラーになったというだけ。

というわけでクライアントのインスタンスを生成する際に、引数にクレデンシャルを渡す事にした。

※実際にエラーが出たGitHub ActionsのJobは以下。

https://github.com/yuta-katayama-23/node-express/runs/6433246022?check_suite_focus=true

### ②`TypeError: Cannot read properties of undefined (reading 'byteLength')`というエラーになる
①の解決のために、クレデンシャルを渡せばいいと思い、以下の[修正](https://github.com/yuta-katayama-23/node-express/commit/39dbc4dcf985c62e5fc04ed986f608fb58742d52)を行った。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/a5acab97-7426-65f7-9f68-214cad0cd02c.png)

がしかし、今度は`TypeError: Cannot read properties of undefined (reading 'byteLength')`というエラーが出た…。エラーの内容をそのまま素直に読んでも？？になり、ググると同じような事で躓いている人がいたので見てみると、以下のようなサイトがあった。

https://github.com/aws/aws-sdk-js-v3/issues/2411

どうやら解決方法としては、キーをcamelCaseにすればいいという事らしい。

| before | after |
|:-:|:-:|
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/8ad97d04-dbb9-012d-0771-036293bb212c.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/ca4ca2da-8aa9-8d3c-a812-ec69bff99b78.png) |

この修正をして再度CIを回してみると確かにエラーは解消された（他のエラーは出ているが）。

https://github.com/yuta-katayama-23/node-express/runs/6433285896?check_suite_focus=true

が、一体全体ケースが違うだけの単語は全く同じキーがあるのは何なのか…？

次の章でその正体についてみていく。

### accessKeyId/secretAccessKey vs AccessKeyId/SecretAccessKey　2つの違いは何？
今回はDynamoDBのクライアントを生成しようとしてたが、そのクライアントの生成時にコンストラクタの引数に渡すconfigについては[Class DynamoDBClient](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/classes/dynamodbclient.html#constructor)から[DynamoDBClientConfig](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/interfaces/dynamodbclientconfig.html)である事が分かり、さらにそのconfigには[credentials](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/interfaces/dynamodbclientconfig.html#credentials)というキーがある事が分かる。

このcredentials（[Namespace Credentials](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/modules/credentials.html)）を見てみると、以下の画像のように問題になっているケースが違うだけの同じ名前のキーがある事が分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1372684/07bb334e-f08a-787c-4d2f-1c8ee0845df2.png)

今回問題になっているのは上記の2つがそれぞれ何者なのか？という事。リファレンスの説明を読むと、

- AccessKeyId<br>The access key ID that identifies the temporary security credentials.（一時的なセキュリティ認証情報を識別するためのアクセスキーID）<br>Defined in clients/client-sts/dist-types/models/models_0.d.ts:283
- SecretAccessKey<br>The secret access key that can be used to sign requests.（リクエストに署名するために使用できる秘密のアクセスキー）<br>Defined in clients/client-sts/dist-types/models/models_0.d.ts:287

と書かれており、`一時的なセキュリティ認証情報`や`client-sts`と書かれているではないか？！という事に気づく。

ここまで行くと察しがつき、`AccessKeyId/SecretAccessKey`はAWS STSの一時的なセキュリティ認証情報を使用するために`AssumeRole`APIなどを呼んだ際に返ってくるCredentialsのキーという事が分かる。

公式で言うと[AWS SDK での一時的セキュリティ認証情報の使用](https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_temp_use-resources.html#using-temp-creds-sdk)や[AssumeRole Response Elements](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html#API_AssumeRole_ResponseElements)に書かれている 。

```
assumeRoleResult = AssumeRole(role-arn);
tempCredentials = new SessionAWSCredentials(
   assumeRoleResult.AccessKeyId, // <- ここで出てくる`AccessKeyId`の事
   assumeRoleResult.SecretAccessKey, // <- ここで出てくる`SecretAccessKey`の事
   assumeRoleResult.SessionToken);
s3Request = CreateAmazonS3Client(tempCredentials);
```

つまり、`AccessKeyId/SecretAccessKey`の2つはSDKにクレデンシャル情報を渡すためのキーではなく、AWS STSから返されるCredentialsのキーを表している。なので、SDKのクライアントを生成するために、コンストラクタに渡すべきクレデンシャル情報は以下のように、`accessKeyId/secretAccessKey`で書く必要がある。

```json
"credentials": {
	"accessKeyId": "dumy",
	"secretAccessKey": "dumy"
}
```

- 参考：[API呼び出しに使用する認証情報をセット](https://zenn.dev/mn87/articles/623f9ebe0435b8#api%E5%91%BC%E3%81%B3%E5%87%BA%E3%81%97%E3%81%AB%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B%E8%AA%8D%E8%A8%BC%E6%83%85%E5%A0%B1%E3%82%92%E3%82%BB%E3%83%83%E3%83%88)

## まとめとして
AWSさん、ドキュメントは分かりやすくして頂けると嬉しいです…(´;ω;｀)と思ったのでした。少なくとも[Namespace Credentials](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-dynamodb/modules/credentials.html)のページに、`AccessKeyId/SecretAccessKey`が書いてあるのは混乱を招くかと…。

※ちなみに、今回の記事ではSDKのクライアントのコンストラクタにクレデンシャルを渡していたが、別にそんな事をせず、環境変数でAWSクレデンシャルを設定すればそれで済むという話でもある（[Node.js での認証情報の設定](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/setting-credentials-node.html)を参照）。

```js
// 以下のように"credentials"を渡さずとも、環境変数で設定する方法もある
client.dynamodb = new DynamoDBClient({
			region: 'local-env',
			endpoint: 'http://localhost:8000',
			credentials: { accessKeyId: 'dumy', secretAccessKey: 'dumy' }
		});
```