1.0.0 • Published 3 years ago

wcxc v1.0.0

Weekly downloads
-
License
MIT
Repository
-
Last release
3 years ago

#API Testing Framework

Quick review

This framework is designed for end-to-end tests on API level. You cannot interact with UI elements (please refer to Sled, Java framework or any other relevant tool for this)

Currently only REST API is supported. It is possible that in future we also support RPC calls. For now, if you need to test a pure RPC endpoint, we recommend you ask the developers to expose it via HTTP. Check with us for the details.

Get Started

To start developing

  • WIX VPN should be connected!
  • npm config set registry http://npm.dev.wixpress.com/ - add WIX registry
  • Install yarn
  • git clone git@github.com:wix-private/api-tests.git
  • yarn in project root directory - download all packages

To run tests

  • yarn test - run all the tests from 'test' folder
  • jest -t 'describeString itString' - run a test named itString from suite named describeString
  • jest -t 'itString' - run a test named itString if there's no describe block
  • jest path/to/file/with/tests.spec.ts - run all tests in the specified class

To create your first test

  1. Copy CURL of the request you want to test.
  2. Run yarn curl:to-test-code <paste your curl here> - it will print the code you need for your first test.
  3. Copy the output or just simply add | pbcopy (for MacOS) at the end of the previous command - it will copy the output instead of printing it (yarn curl:to-test-code <your curl> | pbcopy)
  4. Create a file with .spec.ts extension in the src/test/ folder.
  5. Paste the copied code to the file.
  6. Add/modify assertions.

To create next tests

It's also possible to convert a CURL to a request code only and add use it in your tests. In order to to it:

  1. Add an empty test to your .spec.ts file.
  2. Copy CURL of the request you want to test.
  3. Run yarn curl:to-request-code <paste your curl here> | pbcopy - it will copy the request code.
  4. Paste the copied code to your test.
  5. Add assertions you need.

Execute CURL via the framework

  1. Copy CURL of the request you want to test.
  2. Run yarn curl:execute <paste your curl here> - response will be printed

To create a service

We use services as a layer between tests and http core. The services contain functions which can be used and reused in tests.

Let's assume we have the following test:

it("sends post request as example", async function () {
  const request = new HttpRequest();
  request.method = HttpMethods.POST;
  request.baseUrl = "https://enevqalovpju.x.pipedream.net";
  request.addHeader("Content-Type", "application");
  request.body = { name: "Yoda" };

  const response = await send(request);

  const expected = '{"success":true}';

  expect(JSON.stringify(response.parsedBody)).to.be.eq(expected);
  expect(response.status).to.be.eq(200);
  expect(response.statusText).to.be.eq("OK");
});

And we need to create a service out of it. Let's do it:

  1. Create a service class in src/main/drivers ExampleService.ts
  2. Inherit your service class from BaseService
    export class ExampleService extends BaseService {}
  3. Add a constructor
    constructor() {
       super(baseUrl);
    }
    where baseUrl is the part of endpoint URL which is common for all functions in the service. You can also add an Identity here.
  4. Create a function in a class which will send your API call and return a response
    async exampleCall(): Promise<HttpResponse<any>> {
    }
    HttpResponse has type any now so it will return just general object. It is enough for the tests where you don't need to get access to the response's payload. Otherwise, we need to make it return the type we expect in response (the type should have all the fields returned object has).
    For the most cases Ambassador library has needed type, you just have to find it. In order to do it start typing the type you expect in response inside the HttpResponse <> generic brackets instead of any type. It should look lke <HttpResponse <GetTransactionResponse>
    For other cases create needed type in src/main/data/types.ts and use it.
    export type SuccessResponse = {
      success: boolean;
    };
  5. Now you can move all the code regarding API call from your test to the function:

    async exampleCall(): Promise<HttpResponse<SuccessResponse>> {
        const request = new HttpRequest();
        request.method = HttpMethods.POST;
        request.baseUrl = 'https://enevqalovpju.x.pipedream.net';
        request.addHeader("Content-Type", "application");
        request.body = {"name": "Yoda"};
    
        return send<SuccessResponse>(request);
    }
  6. You can also override any fields of request body by passing PartialOf as an argument to the function and merge it with default body inside:

    async exampleCall(customPayload?: PartialOf<SuccessRequest>): Promise<HttpResponse
    <SuccessResponse>> {
        const request = new HttpRequest();
        request.method = HttpMethods.POST;
        request.baseUrl = 'https://enevqalovpju.x.pipedream.net';
        request.addHeader("Content-Type", "application");
        const defaultBody = {"name": "Yoda"};
        request.body = merge(defaultBody, customPayload);
    
        return send<SuccessResponse>(request);
    }

    If your body JSON is large it is highly recommended to split it by objects and build your body out of them. The example can be found in TransactionService.ts

  7. Now you are ready to modify your test using the created service:

    it('sends post request as example', async function () {
       const service = new ExampleService();
       const customPayload: PartialOf<SuccessRequest> = {
           name: "Anakin"
       };
       const response = await service.exampleCall();
       expect(response.parsedBody.success).to.be.true;
       expect(response.status).to.be.eq(200);
       expect(response.statusText).to.be.eq("OK");
    }

Features

Simple interface for handling REST requests/responses

  • Add all required headers and cookies
  • Add body as a simple JSON-like object or a typed object (see section about wix-ambassador)
  • Add all required authorizations (as user, as app, etc.) with identities (see Identities section)
const request = new HttpRequest();
request.method = HttpMethods.POST;
request.baseUrl = "https://enevqalovpju.x.pipedream.net";
request.addHeader("Content-Type", "application");
request.body = { name: "Yoda" };

const response = await send(request);

You can validate the status of your response:

expect(response.status).to.eq(200);
expect(response.statusText).to.eq("My successful response");

The data you received from the REST endpoint is available in response.parsedBody

expect(response.parsedBody.transaction.authorization.status).to.eq("SUCCEEDED");

Identities

Many WIX endpoints require that the call is authorized either by user (wixSession2 cookie), or by app calling this endpoint (Authorization header). You can add one or several identities to your requests.

App Identity

  1. Precondition: you need an app in dev.wix.com with all necessary permissions
  2. Store your app data in code as AppInstanceData (see existing apps for details)
  3. Check the required authorization token prefix in Fire Console (can be SRV.JWS, or JWT, or else).
  4. Create an AppIdentity object with required authorization token:

    const appIdentity = new AppIdentity(app, "SRV.JWS");
  5. Add this identity to your request:

    request.addIdentities(appIdentity);

User Identity

  1. Store your test user data in code as UserData
  2. Create a UserIdentity object:

    const userIdentity = new WixUserIdentity(user);
  3. Add this identity to your request:

    request.addIdentities(userIdentity);

Type safety with @wix-ambassador

@wix-ambassador is a library which provides types for all requests and responses used in Wix services. Example:

// Type describing an item purchased via wix-payments
// @wix-ambassador library
export interface V3Item {
  category?: V3ItemCategory;
  id?: String;
  quantity?: Int;
  title?: String;
  unitPrice?: Long;
}

// Using it in our code
export const item: V3Item = {
  id: "ItemID",
  title: "ItemTitle",
  unitPrice: 100,
  quantity: 1,
  category: V3ItemCategory.DIGITAL,
};

We use types from wix-ambassador to send requests:

// Charge for existing order
import {
  ChargeForOrderRequest,
  ChargeIntent,
} from "@wix/ambassador-payment-services-web/types";

const request = this.getBaseRequest();
const payload: ChargeForOrderRequest = {
  initializeOnly: false,
  intent: ChargeIntent.SALE,
  orderId: testOrderId,
  payment: testPayment,
};
request.body = payload;

And to get and validate responses:

const result: HttpResponse<ChargeResponse> = await chargeForOrder(
  orderId,
  CARDS.wixPaymentsCard
);
expect(result.status).toBe(200);
expect(result.parsedBody.charge.state.transactionStatus.status).to.be.eq(
  "APPROVED"
);

Partial types

When you send a custom request, you don't always want to specify all fields. Most of them can have default values. For this, we introduced PartialOf<SomeType>

Usage:

  • We will take CaptureTransaction method as an example
  • It requires @wix-ambassador type CaptureTransactionRequest
  • If user doesn't provide any custom data, we will send the default payload:

      const defaultPayload: CaptureTransactionRequest = {
          testMode: true,
          accountId: tenantAccountId,
          transactionId: transactionId
      };
  • In tests, however, we need to send custom data (e.g. capture a certain amount of money, not all of them)

  • But all the other fields can have default value
  • For this, we use the type PartialOf<CaptureTransactionRequest>:

      const payloadWithCustomAmount: PartialOf<CaptureTransactionRequest> = {
          amount: 50
      };
  • PartialOf<SomeType> suggests you all fields from SomeType and checks that you don't make mistakes in their names. But all these fields become optional, and you no longer need to set all of them if you need to change just 1.

Assertions

We use Chai library to make all checks in our tests. Full Chai documentation can be found in https://www.chaijs.com/api/

Chai also provides a list of plugins for specific assertions (for example, validating arrays).

To add a new Chai plugin, you can do the following:

  1. Check that this plugin is not yet present in the framework (check dependencies in package.json)
  2. If it is already added, find its usage in some .spec.ts file and copy it.
  3. Otherwise, run a shell command yarn add %package-name%
  4. It is also recommended to run yarn add @types/%package-name% to add Typescript support. Note that not all plugins provide that!
  5. In your .spec.ts file, add the following imports:

    import chai from 'chai';
    import YourChaiPlugin = require('chai-plugin-library-name');
    chai.use(YourChaiPlugin);
  6. If something goes wrong, and the previous step won't compile, ask framework owners, and we'll add this library for you.

How to store sensitive data

Passwords, secret keys or any other sensitive information can't be stored in repo in as a plain text - it should be encrypted. For encryption/decryption we use AUTOMATION_MASTER_KEY which should be set in local env. To encrypt a key execute the following script in CLI:

$ yarn secret:encrypt "your-secret-key"

To encrypt an object:

$ yarn secret:encrypt-object '{"key":"value"}'

Note that both key and value should be in quotes.

Copy the output and store it in the src/main/core/utils/secret.ts file. To get decrypted value create a function in the same file with the following code:

return simpleCrypto.decrypt("your encrypted value here");

The function will return your original sensitive info.

You can also decrypt your encrypted key using CLI. Use it just to check that your encrypted key decrypts correctly.

$ yarn secret:decrypt "your-encrypted-key"

or

$ yarn secret:decrypt-object "your-encrypted-object"

How to add your tests to Team City run

  1. Add a script to package.json with the following content:

     "test:name-of-entity-you-are-testing": "script-which-runs-your-tests"

    How to create a script to run dedicated tests

  2. In Team City create a build configuration based on AIT API Tests template.

  3. Add Build Step -> Command Line.
  4. Add custom script:
    yarn test:name-of-entity-you-are-testing --testResultsProcessor=jest-teamcity-reporter
  5. Add Trigger -> Schedule Trigger.
  6. Set the schedule to run the tests.