{
  "info": {
    "name": "Терморос — Партнёрское API v1",
    "description": "Проверка публичного API партнёров (/api/v1/catalog/).\n\nПеред запуском заполните переменную коллекции `partnerApiKey` (ключ из админки: Контент → Highload-блоки → Партнёрские API-ключи).\n\nЗапускать можно целиком через Runner — запросы идут в правильном порядке (304-проверка использует ETag из первого запроса), у каждого есть автотесты.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "baseUrl", "value": "https://www.termoros.com" },
    { "key": "partnerApiKey", "value": "ВСТАВЬТЕ_КЛЮЧ_ПАРТНЁРА" },
    { "key": "brandFilter", "value": "Danfoss" },
    { "key": "lastEtag", "value": "" }
  ],
  "item": [
    {
      "name": "Партнёрское API v1",
      "item": [
        {
          "name": "1. Выгрузка JSON",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('Content-Type — JSON', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json'));",
                  "const body = pm.response.json();",
                  "pm.test('meta заполнена', () => {",
                  "    pm.expect(body.meta).to.be.an('object');",
                  "    pm.expect(body.meta.items_count).to.be.above(0);",
                  "    pm.expect(body.meta.currency).to.eql('RUB');",
                  "    pm.expect(body.meta.partner).to.be.a('string').and.not.empty;",
                  "});",
                  "pm.test('items — непустой массив, счётчик совпадает', () => {",
                  "    pm.expect(body.items).to.be.an('array').and.not.empty;",
                  "    pm.expect(body.items.length).to.eql(body.meta.items_count);",
                  "});",
                  "pm.test('структура позиции', () => {",
                  "    const it = body.items[0];",
                  "    ['article','xml_id','name','brand','product_type','unit','url','price_retail','price_partner','discount_percent','stocks'].forEach(f => pm.expect(it, 'поле ' + f).to.have.property(f));",
                  "});",
                  "pm.test('дилерская цена корректна на всей выборке', () => {",
                  "    body.items.slice(0, 500).forEach(it => {",
                  "        if (it.discount_percent > 0) {",
                  "            const expected = Math.round(it.price_retail * (1 - it.discount_percent / 100) * 100) / 100;",
                  "            pm.expect(Math.abs(it.price_partner - expected), it.article).to.be.below(0.011);",
                  "        } else {",
                  "            pm.expect(it.price_partner, it.article).to.eql(it.price_retail);",
                  "        }",
                  "    });",
                  "});",
                  "pm.test('есть хотя бы одна позиция со скидкой (ключ привязан к партнёру с соглашением)', () => {",
                  "    pm.expect(body.items.some(it => it.discount_percent > 0)).to.be.true;",
                  "});",
                  "pm.collectionVariables.set('lastEtag', pm.response.headers.get('ETag'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=json",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "format", "value": "json" } ]
            }
          }
        },
        {
          "name": "2. Повторный запрос с ETag → 304",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 304 (данные не менялись — тело не передаётся)', () => pm.response.to.have.status(304));",
                  "pm.test('тело пустое', () => pm.expect(pm.response.text()).to.be.empty);"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              { "key": "X-Api-Key", "value": "{{partnerApiKey}}" },
              { "key": "If-None-Match", "value": "{{lastEtag}}" }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=json",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "format", "value": "json" } ]
            }
          }
        },
        {
          "name": "3. Выгрузка CSV (Excel)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('Content-Type — text/csv', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('text/csv'));",
                  "pm.test('Content-Disposition — attachment с именем файла', () => pm.expect(pm.response.headers.get('Content-Disposition')).to.include('attachment').and.include('termoros-price-'));",
                  "const text = pm.response.text();",
                  "pm.test('UTF-8 BOM для Excel', () => pm.expect(text.charCodeAt(0)).to.eql(0xFEFF));",
                  "pm.test('заголовок: колонки через «;», есть цены и склады', () => {",
                  "    const head = text.slice(1).split('\\r\\n')[0];",
                  "    pm.expect(head).to.include('Артикул;');",
                  "    pm.expect(head).to.include('Цена розничная');",
                  "    pm.expect(head).to.include('Цена дилерская');",
                  "    pm.expect(head).to.include('Москва');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=csv",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "format", "value": "csv" } ]
            }
          }
        },
        {
          "name": "4. Выгрузка XML",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "pm.test('Content-Type — application/xml', () => pm.expect(pm.response.headers.get('Content-Type')).to.include('application/xml'));",
                  "const text = pm.response.text();",
                  "pm.test('корень <catalog> с атрибутами', () => {",
                  "    pm.expect(text).to.include('<catalog ');",
                  "    pm.expect(text).to.include('currency=\"RUB\"');",
                  "    pm.expect(text).to.include('items_count=');",
                  "});",
                  "pm.test('есть позиции и цены', () => {",
                  "    pm.expect(text).to.include('<item ');",
                  "    pm.expect(text).to.include('<price_partner>');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=xml",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "format", "value": "xml" } ]
            }
          }
        },
        {
          "name": "5. Фильтр по складам (store=1,6)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('в остатках только Москва и Санкт-Петербург', () => {",
                  "    const allowed = ['Москва', 'Санкт-Петербург'];",
                  "    body.items.slice(0, 1000).forEach(it => {",
                  "        Object.keys(it.stocks).forEach(city => pm.expect(allowed, it.article).to.include(city));",
                  "    });",
                  "});",
                  "pm.test('позиции при этом не выпадают (фильтр сужает склады, не товары)', () => pm.expect(body.meta.items_count).to.be.above(0));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=json&store=1,6",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [
                { "key": "format", "value": "json" },
                { "key": "store", "value": "1,6" }
              ]
            }
          }
        },
        {
          "name": "6. Фильтр по бренду",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "const brand = pm.collectionVariables.get('brandFilter');",
                  "pm.test('все позиции — бренд ' + brand, () => {",
                  "    pm.expect(body.items).to.be.an('array').and.not.empty;",
                  "    body.items.forEach(it => pm.expect(it.brand, it.article).to.eql(brand));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=json&brand={{brandFilter}}",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [
                { "key": "format", "value": "json" },
                { "key": "brand", "value": "{{brandFilter}}" }
              ]
            }
          }
        },
        {
          "name": "7. Без ключа → 401",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 401', () => pm.response.to.have.status(401));",
                  "pm.test('код ошибки unauthorized', () => pm.expect(pm.response.json().error.code).to.eql('unauthorized'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ]
            }
          }
        },
        {
          "name": "8. Неверный ключ → 401",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 401', () => pm.response.to.have.status(401));",
                  "pm.test('код ошибки unauthorized', () => pm.expect(pm.response.json().error.code).to.eql('unauthorized'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "0000000000000000000000000000000000000000" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ]
            }
          }
        },
        {
          "name": "9. Неверный формат → 400",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
                  "pm.test('код ошибки bad_format', () => pm.expect(pm.response.json().error.code).to.eql('bad_format'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?format=tsv",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "format", "value": "tsv" } ]
            }
          }
        },
        {
          "name": "10. Неверный склад → 400 со списком допустимых",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 400', () => pm.response.to.have.status(400));",
                  "const err = pm.response.json().error;",
                  "pm.test('код ошибки bad_filter', () => pm.expect(err.code).to.eql('bad_filter'));",
                  "pm.test('в details — что не так и список допустимых складов', () => {",
                  "    pm.expect(err.details.store).to.include('99');",
                  "    pm.expect(err.details.allowed['1']).to.eql('Москва');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Api-Key", "value": "{{partnerApiKey}}" } ],
            "url": {
              "raw": "{{baseUrl}}/api/v1/catalog/?store=99",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "catalog", "" ],
              "query": [ { "key": "store", "value": "99" } ]
            }
          }
        },
        {
          "name": "11. Старое API → 410 Gone",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 410', () => pm.response.to.have.status(410));",
                  "const err = pm.response.json().error;",
                  "pm.test('код gone и ссылка на новую документацию', () => {",
                  "    pm.expect(err.code).to.eql('gone');",
                  "    pm.expect(err.message).to.include('/api/v1/docs/');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/catalog/?brand=Rehau&output=json",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "catalog", "" ],
              "query": [
                { "key": "brand", "value": "Rehau" },
                { "key": "output", "value": "json" }
              ]
            }
          }
        },
        {
          "name": "12. Документация (301 → публичная инструкция)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('после редиректа — 200', () => pm.response.to.have.status(200));",
                  "pm.test('это страница инструкции по API', () => pm.expect(pm.response.text()).to.include('API каталога'));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/api/v1/docs/",
              "host": [ "{{baseUrl}}" ],
              "path": [ "api", "v1", "docs", "" ]
            }
          }
        }
      ]
    }
  ]
}
