Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
172 changes: 172 additions & 0 deletions BrunoCollections/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Bruno Collections

[Bruno](https://www.usebruno.com/) versions of the collections in [`PostmanCollections/`](../PostmanCollections),
generated from them by the pipeline in [`_tools/`](_tools).

Collections are written in the **OpenCollection** format (`opencollection: 1.0.0`) — the YAML
format Bruno now uses by default. Each API is a **separate, self-contained collection**,
mirroring the one-file-per-API layout of `PostmanCollections/`:

```
BrunoCollections/
├── workspace.yml # links all 92 collections; open this to load them at once
└── VTEX - Audience API/
├── opencollection.yml # collection manifest, collection-level auth + docs
├── environments/
│ └── VTEX.yml # accountName, environment, baseUrl, secrets
└── Audience Manager/ # one folder per Postman folder
├── folder.yml
└── Fetch audience.yml # one .yml file per request
```

A request file looks like this:

```yaml
info:
name: Fetch audience
type: http
seq: 1

http:
method: POST
url: "{{baseUrl}}/api/audience-manager/pvt/audience"
headers:
- name: Content-Type
value: application/json
auth: inherit

runtime:
scripts:
- type: tests
code: |-
test("... - Status code is 2xx", function () { ... });

settings:
encodeUrl: true

docs: |-
...
```

## Opening a collection

**All at once — recommended.** The 92 collections are linked by
[`workspace.yml`](workspace.yml), which Bruno loads through the **workspace switcher in the
title bar** — not through the sidebar's collection menu:

1. Click the workspace name in the Bruno **title bar** (top of the window, showing
*My Workspace* by default).
2. Under the **Workspaces** heading, choose **Open workspace**.
3. Select the `BrunoCollections/` directory itself.

All 92 collections appear in the sidebar together.

**A single API.** Use the sidebar's **Open Collection**, navigate *into* `BrunoCollections/`, and
pick the folder you want (for example `BrunoCollections/VTEX - Audience API`). That picker allows
multiple selections, so you can ⌘-click several API folders.

> **Open Collection** must be pointed at an API folder, never at `BrunoCollections/`. Each API is
> a separate collection and the container is a *workspace*, not a collection, so it holds no
> `opencollection.yml` of its own — selecting it fails with *"The collection is not valid (neither
> bruno.json nor opencollection.yml found)"*. That directory is only openable via **Open
> workspace** above. A third action, **Import Collection**, is different again: it expects a
> Postman/OpenAPI file or a ZIP, and these collections are opened, not imported.

From the CLI:

```bash
cd "BrunoCollections/VTEX - Audience API"
bru run . -r --env VTEX
```

## Configuring an environment

Every collection ships a `VTEX` environment holding the variables the requests interpolate:

| Variable | Purpose |
| --- | --- |
| `accountName` | Your VTEX account name (defaults to `apiexamples`) |
| `environment` | `vtexcommercestable` |
| `baseUrl` | `https://{{accountName}}.{{environment}}.com.br` |
| `apiKey` | Secret — your [API key](https://developers.vtex.com/docs/guides/api-authentication-using-api-keys) |

Select the `VTEX` environment, set `accountName`, and fill in the secret values.

> **Note:** the source OpenAPI schemas declare only the `X-VTEX-API-AppKey` security scheme,
> so that is all the Postman collections carry and all that was converted. Most VTEX endpoints
> also require an `X-VTEX-API-AppToken` header — add it as a collection-level header, plus an
> `apiToken` secret variable, if you intend to actually call the APIs.

Secret variables are declared as `secret: true` with no value, so Bruno keeps their values out
of the collection files and out of git.

## What was converted

| | |
| --- | --- |
| Collections | 92 |
| Folders | 297 |
| Requests | 1183 |
| Environments | 92 |

Carried over: HTTP method and URL, path and query parameters (disabled ones marked
`disabled: true`), headers, collection- and request-level auth (`apikey` / `bearer`, with
requests otherwise set to `auth: inherit`), request bodies (JSON, XML, text, form-url-encoded,
multipart, file), and per-request docs.

**Tests.** The `pm.*` assertions Postman collections carry (generated by
[portman](https://github.com/apideck-libraries/portman)) do not run in Bruno, so all 1126
test blocks were rewritten against Bruno's `test()` / `expect()` API — status-code,
`Content-Type`, JSON-body, empty-body, header-presence and JSON-schema assertions. The 804
JSON-schema checks compile through `ajv`, which Bruno bundles and which resolves even under
the default `safe` script sandbox:

```js
test("... - Schema is valid", function () {
const schema = { /* ... */ };
const validate = ajv.compile(schema);
const valid = validate(res.getBody());
expect(valid, JSON.stringify(validate.errors)).to.be.true;
});
```

**Docs.** Each request's `docs` block holds its description, a table of parameter/header
descriptions (the format has no per-parameter comments), and the first `2xx` response example.
Examples larger than 4 KB are referenced rather than inlined, to keep the files readable — the
full set stays available in `PostmanCollections/`.

### Known gaps

- Bruno has no equivalent of Postman's saved response examples, so the non-`2xx` examples and
the additional `2xx` ones are not represented here.
- Two requests (`Create/Update multiple SKU promotion`) carry a `file` body with no path,
because the Postman source has none. Pick a file in Bruno before sending them.

## Regenerating

`PostmanCollections/` is refreshed automatically by the
[`portman` workflow](../.github/workflows/portman.yml) whenever an OpenAPI schema changes, so
this directory drifts as those files are updated. To rebuild it from the current sources:

```bash
./BrunoCollections/_tools/convert.sh
```

The pipeline runs in two stages:

1. [`_tools/postman-to-bruno.py`](_tools/postman-to-bruno.py) turns the Postman v2.1 JSON into
Bruno `.bru` files in a scratch directory — this is where the URL building, test translation
and docs assembly happen.
2. [`_tools/bru-to-opencollection.js`](_tools/bru-to-opencollection.js) rewrites those into the
OpenCollection `.yml` files that ship here, using
[`@usebruno/filestore`](https://www.npmjs.com/package/@usebruno/filestore) — the same package
the Bruno app and the `bru` CLI use to write these files, so the output tracks the format
rather than a hand-rolled approximation of it.

3. [`_tools/make-workspace.js`](_tools/make-workspace.js) writes the `workspace.yml` that links
the generated collections, matching the file Bruno itself writes when it edits a workspace.

The `.bru` tree is an intermediate and is discarded. `convert.sh` installs the Node dependencies
on first run (`_tools/node_modules/`, git-ignored) and needs `python3` and `node`. It rewrites
the collection directories from scratch, so local edits to the generated files are discarded;
`README.md` and `_tools/` are left alone.
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
info:
name: Track ad clicks
type: http
seq: 2

http:
method: POST
url: "{{baseUrl}}/v1/beacon/click/:ad_id"
headers:
- name: Content-Type
value: application/json
- name: Accept
value: application/json
params:
- name: pos
value: "1"
type: query
disabled: true
- name: ad_id
value: 4a94bc6e-7db1-425f-8430-cb4d17488b3b
type: path
body:
type: json
data: |-
{
"user_id": "6f92d1e9-00b6-4f8b-9645-faeab321e1cc"
}
auth: inherit

runtime:
scripts:
- type: tests
code: |-
const Ajv = require("ajv");
const addFormats = require("ajv-formats");
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);

test("[POST]::/v1/beacon/click/:ad_id - Status code is 2xx", function () {
expect(res.getStatus()).to.be.at.least(200);
expect(res.getStatus()).to.be.below(300);
});

test("[POST]::/v1/beacon/click/:ad_id - Content-Type is application/json", function () {
expect(String(res.getHeader("content-type"))).to.include("application/json");
});

test("[POST]::/v1/beacon/click/:ad_id - Response has JSON Body", function () {
const data = res.getBody();
expect(data).to.not.be.undefined;
expect(data).to.not.be.null;
});

test("[POST]::/v1/beacon/click/:ad_id - Schema is valid", function () {
const schema = {"type":"object","properties":{"messages":{"type":"array","description":"List of messages from the server.","items":{"type":"string","description":"Message from the server."}}}};
const validate = ajv.compile(schema);
const valid = validate(res.getBody());
expect(valid, JSON.stringify(validate.errors)).to.be.true;
});

settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

docs: |-
Track when a user clicks on an ad. The event URL must not be constructed manually — always use the URL provided from `POST` [Get ads](https://developers.vtex.com/docs/api-reference/vtex-ads-api#post-/v1/rma/-publisher_id-).

## Permissions

This endpoint does not require [License Manager resources](https://help.vtex.com/docs/tutorials/license-manager-resources).

## Parameters

| Name | In | Description |
| --- | --- | --- |
| `ad_id` | path | (Required) Unique identifier of the ad. |
| `pos` | query | Position of the ad. |
| `Content-Type` | header | (Required) Type of the content being sent. |
| `Accept` | header | (Required) HTTP Client Negotiation Accept Header. Indicates the types of responses the client can understand. |

## Example response (`202`)

```json
{
"messages": [
"click will be processed soon"
]
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
info:
name: Track ad impressions
type: http
seq: 1

http:
method: POST
url: "{{baseUrl}}/v1/beacon/impression/:ad_id"
headers:
- name: Content-Type
value: application/json
- name: Accept
value: application/json
params:
- name: pos
value: "1"
type: query
disabled: true
- name: ad_id
value: 4a94bc6e-7db1-425f-8430-cb4d17488b3b
type: path
body:
type: json
data: |-
{
"user_id": "6f92d1e9-00b6-4f8b-9645-faeab321e1cc"
}
auth: inherit

runtime:
scripts:
- type: tests
code: |-
const Ajv = require("ajv");
const addFormats = require("ajv-formats");
const ajv = new Ajv({ allErrors: true, strict: false });
addFormats(ajv);

test("[POST]::/v1/beacon/impression/:ad_id - Status code is 2xx", function () {
expect(res.getStatus()).to.be.at.least(200);
expect(res.getStatus()).to.be.below(300);
});

test("[POST]::/v1/beacon/impression/:ad_id - Content-Type is application/json", function () {
expect(String(res.getHeader("content-type"))).to.include("application/json");
});

test("[POST]::/v1/beacon/impression/:ad_id - Response has JSON Body", function () {
const data = res.getBody();
expect(data).to.not.be.undefined;
expect(data).to.not.be.null;
});

test("[POST]::/v1/beacon/impression/:ad_id - Schema is valid", function () {
const schema = {"type":"object","properties":{"messages":{"type":"array","description":"List of messages from the server.","items":{"type":"string","description":"Message from the server."}}}};
const validate = ajv.compile(schema);
const valid = validate(res.getBody());
expect(valid, JSON.stringify(validate.errors)).to.be.true;
});

settings:
encodeUrl: true
timeout: 0
followRedirects: true
maxRedirects: 5

docs: |-
Track when an ad is rendered on a page. An impression does not determine whether the ad became visible to the user. Visibility is tracked by the separate `view` event. Do not construct the event URL manually. Always use the URL provided from `POST` [Get ads](https://developers.vtex.com/docs/api-reference/vtex-ads-api#post-/v1/rma/-publisher_id-).

>ℹ️ Fire this event whenever a page loads that contains rendered ads.

## Permissions

This endpoint does not require [License Manager resources](https://help.vtex.com/docs/tutorials/license-manager-resources).

## Parameters

| Name | In | Description |
| --- | --- | --- |
| `ad_id` | path | (Required) Unique identifier of the ad. |
| `pos` | query | Position of the ad. |
| `Content-Type` | header | (Required) Type of the content being sent. |
| `Accept` | header | (Required) HTTP Client Negotiation Accept Header. Indicates the types of responses the client can understand. |

## Example response (`202`)

```json
{
"messages": [
"impression will be processed soon"
]
}
```
Loading