Repository: alexcasalboni/aws-lambda-power-tuning (js)

Issue:
Invalid Parameter Type: Expected params. Payload to be a string, Buffer, Stream, Blob, or typed array object.
Hi,
I have an issue when providing this type of weighted payload value:
```json
{
  "lambdaARN": "my_lambda_ARN",
  "powerValues": [
    128,
    256,
    512,
    1024,
    2048,
    3008
  ],
  "num": 10,
  "payload": [
    {
      "payload": {
        "body-json": {
          "dateRanges": [
            {
              "start": "2020-05-01",
              "end": "2020-05-31"
            },
            {
              "start": "2020-04-01",
              "end": "2020-04-30"
            }
          ]
        },
        "params": {
          "path": {
            "tenantId": "DA29004"
          },
          "querystring": {},
          "header": {
            "Authorization": "Bearer some_bearer"
          }
        }
      },
      "weight": 1
    },
    {
      "payload": {
        "body-json": {
          "dateRanges": [
            {
              "start": "2020-05-01",
              "end": "2020-05-31"
            },
            {
              "start": "2020-03-01",
              "end": "2020-03-31"
            }
          ]
        },
        "params": {
          "path": {
            "tenantId": "DA29004"
          },
          "querystring": {},
          "header": {
            "Authorization": "Bearer some_bearer"
          }
        }
      },
      "weight": 1
    },
    {
      "payload": {
        "body-json": {
          "dateRanges": [
            {
              "start": "2020-05-01",
              "end": "2020-05-31"
            },
            {
              "start": "2020-02-01",
              "end": "2020-02-29"
            }
          ]
        },
        "params": {
          "path": {
            "tenantId": "DA29004"
          },
          "querystring": {},
          "header": {
            "Authorization": "Bearer some_bearer"
          }
        }
      },
      "weight": 1
    }
  ],
  "parallelInvocation": false,
  "strategy": "balanced"
}
```

It results with:
```json
"error": {
    "Error": "InvalidParameterType",
    "Cause": "{\"errorType\":\"InvalidParameterType\",\"errorMessage\":\"Expected params.Payload to be a string, Buffer, Stream, Blob, or typed array object\",\"trace\":[\"InvalidParameterType: Expected params.Payload to be a string, Buffer, Stream, Blob, or typed array object\",\"    at ParamValidator.fail (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:50:37)\",\"    at ParamValidator.validatePayload (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:267:10)\",\"    at ParamValidator.validateScalar (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:133:21)\",\"    at ParamValidator.validateMember (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:94:21)\",\"    at ParamValidator.validateStructure (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:75:14)\",\"    at ParamValidator.validateMember (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:88:21)\",\"    at ParamValidator.validate (/var/runtime/node_modules/aws-sdk/lib/param_validator.js:34:10)\",\"    at Request.VALIDATE_PARAMETERS (/var/runtime/node_modules/aws-sdk/lib/event_listeners.js:126:42)\",\"    at Request.callListeners (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:106:20)\",\"    at callNextListener (/var/runtime/node_modules/aws-sdk/lib/sequential_executor.js:96:12)\"]}"
  }
```

Now, I do not know why this is happening and what it actually means. There are three objects in payload array. When I remove one. Execution is successful. It is payload for Lambda used for POST requests.

Pull request description:
Bugfix (2): weighted payload allocation
Fix #97 (again)

#98 did not cover all possible cases, sorry 🙏 

Patch:
diff --git a/lambda/utils.js b/lambda/utils.js
index 0de763e..0bd15c4 100644
--- a/lambda/utils.js
+++ b/lambda/utils.js
@@ -250,16 +250,18 @@ module.exports.generatePayloads = (num, payloadInput) => {
 
         // iterate over weighted payloads and fill the array based on relative weight
         let done = 0;
-        for (let p of payloadInput) {
+        for (let i = 0; i < payloadInput.length; i++) {
+            const p = payloadInput[i];
             var howMany = Math.floor(p.weight * num / total);
             if (howMany < 1) {
                 throw new Error('Invalid payload weight (num is too small)');
             }
-            // if it's an uneven division, make sure the last item fills the remaining gap
-            const howManyWillBeLeft = num - done - howMany;
-            if (howManyWillBeLeft > 0 && howManyWillBeLeft < howMany) {
-                howMany += howManyWillBeLeft;
+
+            // make sure the last item fills the remaining gap
+            if (i === payloadInput.length - 1) {
+                howMany = num - done;
             }
+
             // finally fill the list with howMany items
             payloads.fill(utils.convertPayload(p.payload), done, done + howMany);
             done += howMany;


Test patch:
diff --git a/test/unit/test-utils.js b/test/unit/test-utils.js
index dc780e6..f3fc978 100644
--- a/test/unit/test-utils.js
+++ b/test/unit/test-utils.js
@@ -331,8 +331,8 @@ describe('Lambda Utils', () => {
         });
 
         it('should invoke the processing function without an alias', async() => {
-            const ARN = "arn:aws:lambda:eu-west-1:XXX:function:name";
-            const data = await utils.invokeLambdaProcessor(ARN, "{}");
+            const ARN = 'arn:aws:lambda:eu-west-1:XXX:function:name';
+            const data = await utils.invokeLambdaProcessor(ARN, '{}');
             expect(data).to.be(undefined); // mocked API call
         });
 
@@ -576,6 +576,56 @@ describe('Lambda Utils', () => {
             expect(counters.E).to.be(14);
         });
 
+        it('should return weighted payloads (30/26)', async() => {
+            const weightedPayload = [
+                { payload: {test: '1'}, weight: 1 },
+                { payload: {test: '2'}, weight: 1 },
+                { payload: {test: '3'}, weight: 1 },
+                { payload: {test: '4'}, weight: 1 },
+                { payload: {test: '5'}, weight: 1 },
+                { payload: {test: '6'}, weight: 1 },
+                { payload: {test: '7'}, weight: 1 },
+                { payload: {test: '8'}, weight: 1 },
+                { payload: {test: '9'}, weight: 1 },
+                { payload: {test: '10'}, weight: 1 },
+                { payload: {test: '11'}, weight: 1 },
+                { payload: {test: '12'}, weight: 1 },
+                { payload: {test: '13'}, weight: 1 },
+                { payload: {test: '14'}, weight: 1 },
+                { payload: {test: '15'}, weight: 1 },
+                { payload: {test: '16'}, weight: 1 },
+                { payload: {test: '17'}, weight: 1 },
+                { payload: {test: '18'}, weight: 1 },
+                { payload: {test: '19'}, weight: 1 },
+                { payload: {test: '20'}, weight: 1 },
+                { payload: {test: '21'}, weight: 1 },
+                { payload: {test: '22'}, weight: 1 },
+                { payload: {test: '23'}, weight: 1 },
+                { payload: {test: '24'}, weight: 1 },
+                { payload: {test: '25'}, weight: 1 },
+                { payload: {test: '26'}, weight: 1 },
+            ];
+
+            const counters = {
+                1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0,
+                11: 0, 12: 0, 13: 0, 14: 0, 15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0,
+                21: 0, 22: 0, 23: 0, 24: 0, 25: 0, 26: 0,
+            };
+
+            const output = utils.generatePayloads(30, weightedPayload);
+            expect(output.length).to.be(30);
+
+            output.forEach(payload => {
+                expect(payload).to.be.a('string');
+                counters[JSON.parse(payload).test] += 1;
+            });
+
+            for (let i = 1; i < 26; i++) {
+                expect(counters[i]).to.be(1);
+            }
+            expect(counters[26]).to.be(1 + 4);
+        });
+
     });
 
 });