From 9a9e0aed87732ffc82fd459238c841747eba36bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dennes=20Sch=C3=A4ffler?= Date: Tue, 11 Nov 2025 09:00:50 +0100 Subject: [PATCH] initial commit --- .gitignore | 4 + Adressen.gql | 29 ++++++ Artikel.gql | 15 +++ Artikelgruppen.gql | 10 ++ Aufträge.gql | 15 +++ Belege.gql | 49 ++++++++++ gql todos/Aritkelpreise.gql | 4 + gql todos/Ausgelieferte Artikel.gql | 0 package.json | 23 +++++ readme.md | 120 +++++++++++++++++++++++ run-tests.js | 145 ++++++++++++++++++++++++++++ yarn.lock | 52 ++++++++++ 12 files changed, 466 insertions(+) create mode 100644 .gitignore create mode 100644 Adressen.gql create mode 100644 Artikel.gql create mode 100644 Artikelgruppen.gql create mode 100644 Aufträge.gql create mode 100644 Belege.gql create mode 100644 gql todos/Aritkelpreise.gql create mode 100644 gql todos/Ausgelieferte Artikel.gql create mode 100644 package.json create mode 100644 readme.md create mode 100644 run-tests.js create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2cd0f44 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +.env +*.log + diff --git a/Adressen.gql b/Adressen.gql new file mode 100644 index 0000000..ff33bf5 --- /dev/null +++ b/Adressen.gql @@ -0,0 +1,29 @@ +query { + getAddresses(input: { + take: 10 + }) { + items { + id: id + name: name + first_name: firstName + last_name: lastName + extra: addition + street: primaryPostalAddress { street } + zip_code: primaryPostalAddress { postalCode } + city: primaryPostalAddress { city } + homepage: homepage + phone_number_1: mainPhone + phone_number_2: phone2 + phone_number_3: mobilePhone + phone_number_4: fax + email: defaultEmail + alternative_email: email2 + customer_reference: debitor { identifier } + supplier_reference: creditor { identifier } + sales_lead_reference: referenceId + reference: referenceId + receipt_block: documentBlockOut + inactive: inactive + } + } +} \ No newline at end of file diff --git a/Artikel.gql b/Artikel.gql new file mode 100644 index 0000000..ef67d6c --- /dev/null +++ b/Artikel.gql @@ -0,0 +1,15 @@ +query { + getProducts(input: { take: 10 }) { + items { + phxId: id + id: identifier + name: description + manufacturer_number: mpn + ean: gtin, + extra: addition, + quantity_unit: unit { identifier } + productType + inactive: inactive + } + } +} \ No newline at end of file diff --git a/Artikelgruppen.gql b/Artikelgruppen.gql new file mode 100644 index 0000000..9e6129a --- /dev/null +++ b/Artikelgruppen.gql @@ -0,0 +1,10 @@ +query { + getProductGroups(input: { take: 10 }) { + items { + phxId: id + id: identifier + name: description + parent_id: parentId #not the identifier id + } + } +} \ No newline at end of file diff --git a/Aufträge.gql b/Aufträge.gql new file mode 100644 index 0000000..6faf117 --- /dev/null +++ b/Aufträge.gql @@ -0,0 +1,15 @@ +query { + getDocuments(input: { + take: 10, + documentDefinitionIdentifier: ["SO"] + }) { + items { + id: identifier + documentPostalAddress { + name: recipient + description: addition + } + status + } + } +} \ No newline at end of file diff --git a/Belege.gql b/Belege.gql new file mode 100644 index 0000000..220f10a --- /dev/null +++ b/Belege.gql @@ -0,0 +1,49 @@ +query { + getDocuments(input: { + take: 10, + documentDefinitionIdentifier: ["OF", "SO", "DL", "INV"] + }) { + items { + id: id + number: identifier + address_reference: debitorCreditorIdentifier + documentPostalAddress { + name: recipient + description: addition + city: city + country: countryIso + street: street + zip: postalCode + }, + external_order: theirReference + contact_person: theirSignature + own_reference: ourSignature + address_id: addressId + status + creatorEmployee { + created_by_name: lastName + } + currency: currency + customFields { + custom_field_1: text1 + custom_field_2: text2 + } + date: date, + deliveryPostalAddress { + delivery_city: city + delivery_name: recipient + } + discount: discountPercentage + extra_text: notes + payment_condition: paymentReference + payment_target: paymentTargetDays + price: total + total_price: totalWithTax + profit: revenue + tax: totalTax + created_at: createdAt + receipt_type_id: documentDefinitionIdentifier + weight: grossWeight + } + } +} \ No newline at end of file diff --git a/gql todos/Aritkelpreise.gql b/gql todos/Aritkelpreise.gql new file mode 100644 index 0000000..a56ef02 --- /dev/null +++ b/gql todos/Aritkelpreise.gql @@ -0,0 +1,4 @@ +#TBD +#SELECT * +#FROM AP_ARTICLE_PRICE_CUSTOMER_MATRIX(:date, :articleIds, 'EUR') +#ORDER BY [priority] \ No newline at end of file diff --git a/gql todos/Ausgelieferte Artikel.gql b/gql todos/Ausgelieferte Artikel.gql new file mode 100644 index 0000000..e69de29 diff --git a/package.json b/package.json new file mode 100644 index 0000000..12e36af --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "venabo-gql-templates", + "version": "1.0.0", + "description": "GraphQL test runner for Venabo API", + "main": "run-tests.js", + "type": "module", + "scripts": { + "test": "node run-tests.js", + "test:verbose": "node run-tests.js --verbose" + }, + "keywords": [ + "graphql", + "testing", + "venabo" + ], + "author": "", + "license": "ISC", + "dependencies": { + "chalk": "^5.6.2", + "dotenv": "^16.4.5", + "node-fetch": "^3.3.2" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..b069bd8 --- /dev/null +++ b/readme.md @@ -0,0 +1,120 @@ +# Venabo GraphQL Templates + +A simple JavaScript tool to run and test GraphQL queries against the Venabo API. + +## Features + +- 🚀 Automatically discovers and runs all `.gql` files in the directory +- ✅ Validates GraphQL queries and mutations +- 📊 Provides test results summary +- 🔍 Verbose mode for detailed output +- 🎨 Colorized terminal output + +## Setup + +1. Install dependencies: +```bash +yarn install +``` + +## Usage + +### Run all tests +```bash +yarn test +``` + +Or directly: +```bash +node run-tests.js +``` + +### Run with verbose output +```bash +yarn test:verbose +``` + +Or: +```bash +node run-tests.js --verbose +``` + +### Custom GraphQL Endpoint + +Set the `GRAPHQL_ENDPOINT` environment variable to use a different endpoint: + +```bash +# Windows PowerShell +$env:GRAPHQL_ENDPOINT="https://your-endpoint.com/graphql"; node run-tests.js + +# Windows CMD +set GRAPHQL_ENDPOINT=https://your-endpoint.com/graphql && node run-tests.js + +# Linux/Mac +GRAPHQL_ENDPOINT=https://your-endpoint.com/graphql node run-tests.js +``` + +Default endpoint: `https://venabo.phx-erp.cloud/backend-api/admin-api` + +### Custom Authorization Token + +Set the `AUTH_TOKEN` environment variable to use a different Bearer token: + +```bash +# Windows PowerShell +$env:AUTH_TOKEN="your-token-here"; node run-tests.js + +# Windows CMD +set AUTH_TOKEN=your-token-here && node run-tests.js + +# Linux/Mac +AUTH_TOKEN=your-token-here node run-tests.js +``` + +**Note:** All requests include an `Authorization: Bearer ` header. + +### Using .env File + +You can create a `.env` file in the project root to store your configuration: + +```env +GRAPHQL_ENDPOINT=https://venabo.phx-erp.cloud/backend-api/admin-api +AUTH_TOKEN=your-token-here +``` + +The `.env` file is automatically loaded when you run the tests. Environment variables set in your shell will override values in the `.env` file. + +## Test Files + +Create `.gql` files in the project directory. Each file should contain a valid GraphQL query, mutation, or subscription: + +**Example: `Artikel.gql`** +```graphql +query { + getProducts(input: { take: 10 }) { + items { + id + identifierAlias: identifier + } + } +} +``` + +The tool will automatically: +- Discover all `.gql` files +- Extract the query/mutation/subscription +- Send it to the GraphQL endpoint +- Report success or failure + +## Output + +The tool provides: +- ✅ Success indicators for passing tests +- ❌ Error messages for failing tests +- 📊 Summary statistics at the end +- 🔍 Detailed output in verbose mode (including query content and response data) + +## Exit Codes + +- `0` - All tests passed +- `1` - One or more tests failed diff --git a/run-tests.js b/run-tests.js new file mode 100644 index 0000000..4ae0fd2 --- /dev/null +++ b/run-tests.js @@ -0,0 +1,145 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import fetch from 'node-fetch'; +import dotenv from 'dotenv'; +import chalk from 'chalk'; + +// Load .env file +dotenv.config(); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Configuration +const GRAPHQL_ENDPOINT = process.env.GRAPHQL_ENDPOINT || 'https://venabo.phx-erp.cloud/backend-api/admin-api'; +const AUTH_TOKEN = process.env.AUTH_TOKEN; +const VERBOSE = process.argv.includes('--verbose'); + +function log(message) { + console.log(message); +} + +function parseGraphQLFile(filePath) { + try { + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + + // Extract query name and type + const firstLine = lines[0].trim(); + const queryType = firstLine.startsWith('query') ? 'query' : + firstLine.startsWith('mutation') ? 'mutation' : + firstLine.startsWith('subscription') ? 'subscription' : 'query'; + + return { + name: path.basename(filePath, '.gql'), + type: queryType, + query: content.trim(), + }; + } catch (error) { + throw new Error(`Failed to parse ${filePath}: ${error.message}`); + } +} + +async function runGraphQLTest(test) { + try { + log(`\n${chalk.cyan.bold('Testing:')} ${chalk.cyan(test.name)}`); + + if (VERBOSE) { + log(` ${chalk.gray('Type:')} ${chalk.yellow(test.type)}`); + log(` ${chalk.gray('Query:')}`); + console.log(test.query.split('\n').map(line => ` ${chalk.gray(line)}`).join('\n')); + } + + const response = await fetch(GRAPHQL_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${AUTH_TOKEN}`, + }, + body: JSON.stringify({ + query: test.query, + }), + }); + + const text = await response.text(); + let result; + try { + result = JSON.parse(text); + } catch (e) { + throw new Error(`HTTP ${response.status}: ${response.statusText}. Response: ${text.substring(0, 200)}`); + } + + if (result.errors) { + log(` ${chalk.red.bold('Errors found:')}`); + result.errors.forEach(error => { + log(` ${chalk.red('✗')} ${chalk.red(error.message)}`); + if (VERBOSE && error.locations) { + error.locations.forEach(loc => { + log(` ${chalk.gray(`Line ${loc.line}, Column ${loc.column}`)}`); + }); + } + if (VERBOSE && error.path) { + log(` ${chalk.gray(`Path: ${error.path.join('.')}`)}`); + } + }); + return { success: false, test, result }; + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${response.statusText}`); + } + + log(` ${chalk.green.bold('✓ Success')}`); + if (VERBOSE && result.data) { + log(` ${chalk.gray('Response:')}`); + console.log(JSON.stringify(result.data, null, 2)); + } + + return { success: true, test, result }; + } catch (error) { + log(` ${chalk.red.bold('✗ Failed:')} ${chalk.red(error.message)}`); + return { success: false, test, error: error.message }; + } +} + +async function main() { + log(chalk.bold.blue('Venabo GraphQL Test Runner\n')); + log(`${chalk.gray('Endpoint:')} ${chalk.cyan(GRAPHQL_ENDPOINT)}`); + + // Find all .gql files + const files = fs.readdirSync(__dirname) + .filter(file => file.endsWith('.gql')) + .map(file => path.join(__dirname, file)); + + if (files.length === 0) { + log(chalk.yellow('No .gql files found in the current directory')); + process.exit(0); + } + + log(`${chalk.gray('Found')} ${chalk.cyan(files.length)} ${chalk.gray('test file(s)\n')}`); + + // Parse all test files + const tests = files.map(parseGraphQLFile); + + // Run all tests + const results = await Promise.all(tests.map(runGraphQLTest)); + + // Summary + const passed = results.filter(r => r.success).length; + const failed = results.filter(r => !r.success).length; + + log('\n' + chalk.gray('='.repeat(50))); + log(chalk.bold('Summary:')); + log(` ${chalk.green.bold('Passed:')} ${chalk.green(passed)}`); + log(` ${chalk.red.bold('Failed:')} ${chalk.red(failed)}`); + log(chalk.gray('='.repeat(50)) + '\n'); + + process.exit(failed > 0 ? 1 : 0); +} + +main().catch(error => { + log(`\n${chalk.red.bold('Fatal error:')} ${chalk.red(error.message)}`); + process.exit(1); +}); + diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..7f4cfcb --- /dev/null +++ b/yarn.lock @@ -0,0 +1,52 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +chalk@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +dotenv@^16.4.5: + version "16.6.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" + integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +node-domexception@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" + integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==