- Bump DESK_MOLONI version to 3.0.1 across module - Normalize hooks to after_client_* and instantiate PerfexHooks safely - Fix OAuthController view path and API client class name - Add missing admin views for webhook config/logs; adjust view loading - Harden client portal routes and admin routes mapping - Make Dashboard/Logs/Queue tolerant to optional model methods - Align log details query with existing schema; avoid broken joins This makes the module operational in Perfex (admin + client), reduces 404s, and avoids fatal errors due to inconsistent tables/methods.
771 lines
27 KiB
PHP
771 lines
27 KiB
PHP
<?php
|
|
|
|
defined('BASEPATH') or exit('No direct script access allowed');
|
|
|
|
/**
|
|
* Moloni API Contract Tests
|
|
*
|
|
* Verifies that API implementation matches the OpenAPI specification
|
|
* Tests all endpoints defined in moloni-api.yaml
|
|
*
|
|
* @package DeskMoloni
|
|
* @author Descomplicar®
|
|
* @copyright 2025 Descomplicar
|
|
* @version 3.0.0
|
|
*/
|
|
class MoloniApiContractTest extends PHPUnit\Framework\TestCase
|
|
{
|
|
private $CI;
|
|
private $api_client;
|
|
private $contract_spec;
|
|
private $test_company_id;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
// Get CodeIgniter instance
|
|
$this->CI = &get_instance();
|
|
|
|
// Load API client
|
|
$this->CI->load->library('desk_moloni/moloniapiclient');
|
|
$this->api_client = $this->CI->moloniapiclient;
|
|
|
|
// Load contract specification
|
|
$this->loadContractSpec();
|
|
|
|
// Test company ID
|
|
$this->test_company_id = 12345;
|
|
|
|
// Set up authentication for contract tests
|
|
$this->setupContractAuth();
|
|
}
|
|
|
|
/**
|
|
* Load OpenAPI contract specification
|
|
*/
|
|
private function loadContractSpec()
|
|
{
|
|
$spec_file = FCPATH . '../specs/001-desk-moloni-integration/contracts/moloni-api.yaml';
|
|
|
|
if (file_exists($spec_file)) {
|
|
$this->contract_spec = yaml_parse_file($spec_file);
|
|
} else {
|
|
// Fallback to embedded spec for testing
|
|
$this->contract_spec = $this->getEmbeddedSpec();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up authentication for contract testing
|
|
*/
|
|
private function setupContractAuth()
|
|
{
|
|
$this->CI->load->library('desk_moloni/molonioauth');
|
|
$this->CI->load->library('desk_moloni/tokenmanager');
|
|
|
|
// Configure OAuth
|
|
$this->CI->molonioauth->configure('test_client_id', 'test_client_secret');
|
|
|
|
// Add mock tokens
|
|
$this->CI->tokenmanager->save_tokens([
|
|
'access_token' => 'contract_test_token',
|
|
'expires_in' => 3600
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Test OAuth 2.0 Token Exchange endpoint
|
|
* POST /oauth2/token
|
|
*/
|
|
public function testOAuthTokenExchangeContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('post', '/oauth2/token');
|
|
|
|
// Test authorization_code grant
|
|
$auth_code_data = [
|
|
'grant_type' => 'authorization_code',
|
|
'code' => 'test_auth_code',
|
|
'client_id' => 'test_client_id',
|
|
'client_secret' => 'test_client_secret',
|
|
'redirect_uri' => 'https://test.com/callback'
|
|
];
|
|
|
|
$this->validateRequestSchema($auth_code_data, $endpoint_spec['requestBody']);
|
|
|
|
// Test refresh_token grant
|
|
$refresh_token_data = [
|
|
'grant_type' => 'refresh_token',
|
|
'refresh_token' => 'test_refresh_token',
|
|
'client_id' => 'test_client_id',
|
|
'client_secret' => 'test_client_secret'
|
|
];
|
|
|
|
$this->validateRequestSchema($refresh_token_data, $endpoint_spec['requestBody']);
|
|
|
|
// Validate response schema
|
|
$mock_response = [
|
|
'access_token' => 'access_token_value',
|
|
'token_type' => 'Bearer',
|
|
'expires_in' => 3600,
|
|
'refresh_token' => 'refresh_token_value',
|
|
'scope' => 'read write'
|
|
];
|
|
|
|
$this->validateResponseSchema($mock_response, $endpoint_spec['responses']['200']);
|
|
}
|
|
|
|
/**
|
|
* Test Customers List endpoint
|
|
* GET /customers
|
|
*/
|
|
public function testCustomersListContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('get', '/customers');
|
|
|
|
// Test required parameters
|
|
$params = [
|
|
'company_id' => $this->test_company_id,
|
|
'qty' => 50,
|
|
'offset' => 0
|
|
];
|
|
|
|
$this->validateQueryParameters($params, $endpoint_spec['parameters']);
|
|
|
|
// Validate response structure
|
|
$mock_customers = [
|
|
[
|
|
'customer_id' => 1,
|
|
'number' => 'CUST001',
|
|
'name' => 'Test Customer',
|
|
'vat' => '123456789',
|
|
'email' => 'test@example.com',
|
|
'phone' => '+351912345678',
|
|
'address' => 'Test Address',
|
|
'zip_code' => '4000-000',
|
|
'city' => 'Porto',
|
|
'country_id' => 1,
|
|
'website' => 'https://example.com',
|
|
'notes' => 'Test notes'
|
|
]
|
|
];
|
|
|
|
$this->validateResponseSchema($mock_customers, $endpoint_spec['responses']['200']);
|
|
}
|
|
|
|
/**
|
|
* Test Customer Creation endpoint
|
|
* POST /customers
|
|
*/
|
|
public function testCustomerCreateContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('post', '/customers');
|
|
|
|
// Test valid customer creation data
|
|
$customer_data = [
|
|
'company_id' => $this->test_company_id,
|
|
'name' => 'New Test Customer',
|
|
'vat' => '987654321',
|
|
'email' => 'newcustomer@example.com',
|
|
'phone' => '+351987654321',
|
|
'address' => 'New Address',
|
|
'zip_code' => '1000-000',
|
|
'city' => 'Lisboa',
|
|
'country_id' => 1,
|
|
'website' => 'https://newcustomer.com',
|
|
'notes' => 'New customer notes'
|
|
];
|
|
|
|
$this->validateRequestSchema($customer_data, $endpoint_spec['requestBody']);
|
|
|
|
// Test required fields validation
|
|
$required_fields = ['company_id', 'name', 'vat'];
|
|
foreach ($required_fields as $field) {
|
|
$this->assertArrayHasKey($field, $customer_data, "Required field '{$field}' missing");
|
|
}
|
|
|
|
// Test response schema
|
|
$mock_response = $customer_data;
|
|
$mock_response['customer_id'] = 123;
|
|
$mock_response['number'] = 'CUST123';
|
|
|
|
$this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
|
|
}
|
|
|
|
/**
|
|
* Test Customer Get endpoint
|
|
* GET /customers/{customer_id}
|
|
*/
|
|
public function testCustomerGetContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('get', '/customers/{customer_id}');
|
|
|
|
// Test path parameters
|
|
$customer_id = 123;
|
|
$this->assertIsInt($customer_id);
|
|
|
|
// Test query parameters
|
|
$params = [
|
|
'company_id' => $this->test_company_id
|
|
];
|
|
|
|
$this->validateQueryParameters($params, $endpoint_spec['parameters']);
|
|
|
|
// Test response schema
|
|
$mock_customer = [
|
|
'customer_id' => $customer_id,
|
|
'number' => 'CUST123',
|
|
'name' => 'Retrieved Customer',
|
|
'vat' => '123456789',
|
|
'email' => 'customer@example.com',
|
|
'phone' => '+351912345678',
|
|
'address' => 'Customer Address',
|
|
'zip_code' => '4000-000',
|
|
'city' => 'Porto',
|
|
'country_id' => 1,
|
|
'website' => 'https://customer.com',
|
|
'notes' => 'Customer notes'
|
|
];
|
|
|
|
$this->validateResponseSchema($mock_customer, $endpoint_spec['responses']['200']);
|
|
}
|
|
|
|
/**
|
|
* Test Customer Update endpoint
|
|
* PUT /customers/{customer_id}
|
|
*/
|
|
public function testCustomerUpdateContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('put', '/customers/{customer_id}');
|
|
|
|
// Test update data
|
|
$update_data = [
|
|
'customer_id' => 123,
|
|
'company_id' => $this->test_company_id,
|
|
'name' => 'Updated Customer Name',
|
|
'email' => 'updated@example.com',
|
|
'phone' => '+351999888777',
|
|
'address' => 'Updated Address',
|
|
'city' => 'Braga',
|
|
'notes' => 'Updated notes'
|
|
];
|
|
|
|
$this->validateRequestSchema($update_data, $endpoint_spec['requestBody']);
|
|
|
|
// Test required fields for update
|
|
$required_fields = ['customer_id', 'company_id'];
|
|
foreach ($required_fields as $field) {
|
|
$this->assertArrayHasKey($field, $update_data, "Required field '{$field}' missing");
|
|
}
|
|
|
|
// Test response schema
|
|
$this->validateResponseSchema($update_data, $endpoint_spec['responses']['200']);
|
|
}
|
|
|
|
/**
|
|
* Test Products List endpoint
|
|
* GET /products
|
|
*/
|
|
public function testProductsListContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('get', '/products');
|
|
|
|
// Test parameters
|
|
$params = [
|
|
'company_id' => $this->test_company_id
|
|
];
|
|
|
|
$this->validateQueryParameters($params, $endpoint_spec['parameters']);
|
|
|
|
// Test response schema
|
|
$mock_products = [
|
|
[
|
|
'product_id' => 1,
|
|
'name' => 'Test Product',
|
|
'summary' => 'Product description',
|
|
'reference' => 'PROD001',
|
|
'price' => 99.99,
|
|
'unit_id' => 1,
|
|
'has_stock' => 0,
|
|
'stock' => 0.0,
|
|
'minimum_stock' => 0.0
|
|
]
|
|
];
|
|
|
|
$this->validateResponseSchema($mock_products, $endpoint_spec['responses']['200']);
|
|
}
|
|
|
|
/**
|
|
* Test Product Creation endpoint
|
|
* POST /products
|
|
*/
|
|
public function testProductCreateContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('post', '/products');
|
|
|
|
// Test product creation data
|
|
$product_data = [
|
|
'company_id' => $this->test_company_id,
|
|
'name' => 'New Product',
|
|
'summary' => 'New product description',
|
|
'reference' => 'NEWPROD001',
|
|
'price' => 149.99,
|
|
'unit_id' => 1,
|
|
'has_stock' => 0
|
|
];
|
|
|
|
$this->validateRequestSchema($product_data, $endpoint_spec['requestBody']);
|
|
|
|
// Test required fields
|
|
$required_fields = ['company_id', 'name', 'price'];
|
|
foreach ($required_fields as $field) {
|
|
$this->assertArrayHasKey($field, $product_data, "Required field '{$field}' missing");
|
|
}
|
|
|
|
// Test price is numeric
|
|
$this->assertIsNumeric($product_data['price']);
|
|
|
|
// Test response schema
|
|
$mock_response = $product_data;
|
|
$mock_response['product_id'] = 456;
|
|
|
|
$this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
|
|
}
|
|
|
|
/**
|
|
* Test Invoice Creation endpoint
|
|
* POST /invoices
|
|
*/
|
|
public function testInvoiceCreateContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('post', '/invoices');
|
|
|
|
// Test invoice creation data
|
|
$invoice_data = [
|
|
'company_id' => $this->test_company_id,
|
|
'customer_id' => 123,
|
|
'date' => date('Y-m-d'),
|
|
'expiration_date' => date('Y-m-d', strtotime('+30 days')),
|
|
'document_set_id' => 1,
|
|
'products' => [
|
|
[
|
|
'product_id' => 1,
|
|
'name' => 'Invoice Product',
|
|
'summary' => 'Product for invoice',
|
|
'qty' => 2.0,
|
|
'price' => 50.0,
|
|
'discount' => 0.0,
|
|
'tax' => 23.0
|
|
]
|
|
],
|
|
'notes' => 'Invoice notes'
|
|
];
|
|
|
|
$this->validateRequestSchema($invoice_data, $endpoint_spec['requestBody']);
|
|
|
|
// Test required fields
|
|
$required_fields = ['company_id', 'customer_id', 'date', 'products'];
|
|
foreach ($required_fields as $field) {
|
|
$this->assertArrayHasKey($field, $invoice_data, "Required field '{$field}' missing");
|
|
}
|
|
|
|
// Test products array
|
|
$this->assertIsArray($invoice_data['products']);
|
|
$this->assertNotEmpty($invoice_data['products']);
|
|
|
|
// Test product structure
|
|
$product = $invoice_data['products'][0];
|
|
$product_required_fields = ['product_id', 'name', 'qty', 'price'];
|
|
foreach ($product_required_fields as $field) {
|
|
$this->assertArrayHasKey($field, $product, "Product required field '{$field}' missing");
|
|
}
|
|
|
|
// Test response schema
|
|
$mock_response = [
|
|
'document_id' => 789,
|
|
'number' => 'INV001/2025',
|
|
'date' => $invoice_data['date'],
|
|
'customer_id' => $invoice_data['customer_id'],
|
|
'net_value' => 100.0,
|
|
'tax_value' => 23.0,
|
|
'gross_value' => 123.0,
|
|
'status' => 1,
|
|
'products' => [
|
|
[
|
|
'product_id' => 1,
|
|
'name' => 'Invoice Product',
|
|
'summary' => 'Product for invoice',
|
|
'qty' => 2.0,
|
|
'price' => 50.0,
|
|
'discount' => 0.0,
|
|
'tax' => 23.0
|
|
]
|
|
]
|
|
];
|
|
|
|
$this->validateResponseSchema($mock_response, $endpoint_spec['responses']['201']);
|
|
}
|
|
|
|
/**
|
|
* Test Invoice PDF endpoint
|
|
* GET /invoices/{invoice_id}/getPDF
|
|
*/
|
|
public function testInvoicePdfContract()
|
|
{
|
|
$endpoint_spec = $this->getEndpointSpec('get', '/invoices/{invoice_id}/getPDF');
|
|
|
|
// Test path parameters
|
|
$invoice_id = 789;
|
|
$this->assertIsInt($invoice_id);
|
|
|
|
// Test query parameters
|
|
$params = [
|
|
'company_id' => $this->test_company_id
|
|
];
|
|
|
|
$this->validateQueryParameters($params, $endpoint_spec['parameters']);
|
|
|
|
// For PDF response, we test that it would return binary data
|
|
// In actual implementation, this would be validated differently
|
|
$this->assertTrue(true, 'PDF endpoint contract structure verified');
|
|
}
|
|
|
|
/**
|
|
* Test API base URL and versioning
|
|
*/
|
|
public function testApiBaseUrlContract()
|
|
{
|
|
$expected_base_url = 'https://api.moloni.pt/v1';
|
|
$spec_servers = $this->contract_spec['servers'];
|
|
|
|
$this->assertNotEmpty($spec_servers);
|
|
$this->assertEquals($expected_base_url, $spec_servers[0]['url']);
|
|
}
|
|
|
|
/**
|
|
* Test OAuth 2.0 security scheme
|
|
*/
|
|
public function testOAuth2SecurityScheme()
|
|
{
|
|
$security_schemes = $this->contract_spec['components']['securitySchemes'];
|
|
|
|
$this->assertArrayHasKey('oauth2', $security_schemes);
|
|
|
|
$oauth2_scheme = $security_schemes['oauth2'];
|
|
$this->assertEquals('oauth2', $oauth2_scheme['type']);
|
|
$this->assertArrayHasKey('flows', $oauth2_scheme);
|
|
$this->assertArrayHasKey('authorizationCode', $oauth2_scheme['flows']);
|
|
|
|
$auth_code_flow = $oauth2_scheme['flows']['authorizationCode'];
|
|
$this->assertEquals('https://api.moloni.pt/v1/oauth2/authorize', $auth_code_flow['authorizationUrl']);
|
|
$this->assertEquals('https://api.moloni.pt/v1/oauth2/token', $auth_code_flow['tokenUrl']);
|
|
}
|
|
|
|
/**
|
|
* Test all schema definitions exist
|
|
*/
|
|
public function testSchemaDefinitions()
|
|
{
|
|
$schemas = $this->contract_spec['components']['schemas'];
|
|
|
|
$required_schemas = [
|
|
'TokenResponse',
|
|
'Customer', 'CustomerCreate', 'CustomerUpdate',
|
|
'Product', 'ProductCreate',
|
|
'Invoice', 'InvoiceCreate',
|
|
'InvoiceProduct', 'InvoiceProductCreate'
|
|
];
|
|
|
|
foreach ($required_schemas as $schema) {
|
|
$this->assertArrayHasKey($schema, $schemas, "Schema '{$schema}' not defined");
|
|
$this->assertArrayHasKey('type', $schemas[$schema], "Schema '{$schema}' missing type");
|
|
$this->assertArrayHasKey('properties', $schemas[$schema], "Schema '{$schema}' missing properties");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test API client implementation matches contract
|
|
*/
|
|
public function testApiClientMethodsMatchContract()
|
|
{
|
|
$paths = $this->contract_spec['paths'];
|
|
|
|
// Verify API client has methods for all endpoints
|
|
$this->assertTrue(method_exists($this->api_client, 'list_customers'));
|
|
$this->assertTrue(method_exists($this->api_client, 'get_customer'));
|
|
$this->assertTrue(method_exists($this->api_client, 'create_customer'));
|
|
$this->assertTrue(method_exists($this->api_client, 'update_customer'));
|
|
$this->assertTrue(method_exists($this->api_client, 'list_products'));
|
|
$this->assertTrue(method_exists($this->api_client, 'create_product'));
|
|
$this->assertTrue(method_exists($this->api_client, 'create_invoice'));
|
|
$this->assertTrue(method_exists($this->api_client, 'get_invoice_pdf'));
|
|
}
|
|
|
|
/**
|
|
* Validate request data against schema
|
|
*/
|
|
private function validateRequestSchema($data, $request_body_spec)
|
|
{
|
|
if (!isset($request_body_spec['content']['application/json']['schema'])) {
|
|
return; // No schema to validate against
|
|
}
|
|
|
|
$schema_ref = $request_body_spec['content']['application/json']['schema']['$ref'] ?? null;
|
|
|
|
if ($schema_ref) {
|
|
$schema_name = str_replace('#/components/schemas/', '', $schema_ref);
|
|
$schema = $this->contract_spec['components']['schemas'][$schema_name];
|
|
|
|
$this->validateDataAgainstSchema($data, $schema);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate response data against schema
|
|
*/
|
|
private function validateResponseSchema($data, $response_spec)
|
|
{
|
|
if (!isset($response_spec['content']['application/json']['schema'])) {
|
|
return; // No schema to validate against
|
|
}
|
|
|
|
$schema = $response_spec['content']['application/json']['schema'];
|
|
|
|
if (isset($schema['type']) && $schema['type'] === 'array') {
|
|
$this->assertIsArray($data);
|
|
if (isset($schema['items']['$ref'])) {
|
|
$item_schema_name = str_replace('#/components/schemas/', '', $schema['items']['$ref']);
|
|
$item_schema = $this->contract_spec['components']['schemas'][$item_schema_name];
|
|
|
|
if (!empty($data)) {
|
|
$this->validateDataAgainstSchema($data[0], $item_schema);
|
|
}
|
|
}
|
|
} elseif (isset($schema['$ref'])) {
|
|
$schema_name = str_replace('#/components/schemas/', '', $schema['$ref']);
|
|
$schema_def = $this->contract_spec['components']['schemas'][$schema_name];
|
|
|
|
$this->validateDataAgainstSchema($data, $schema_def);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate query parameters
|
|
*/
|
|
private function validateQueryParameters($params, $parameters_spec)
|
|
{
|
|
foreach ($parameters_spec as $param_spec) {
|
|
if ($param_spec['in'] === 'query' && isset($param_spec['required']) && $param_spec['required']) {
|
|
$param_name = $param_spec['name'];
|
|
$this->assertArrayHasKey($param_name, $params, "Required query parameter '{$param_name}' missing");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate data against schema definition
|
|
*/
|
|
private function validateDataAgainstSchema($data, $schema)
|
|
{
|
|
$this->assertIsArray($data);
|
|
|
|
if (isset($schema['required'])) {
|
|
foreach ($schema['required'] as $required_field) {
|
|
$this->assertArrayHasKey($required_field, $data, "Required field '{$required_field}' missing");
|
|
}
|
|
}
|
|
|
|
if (isset($schema['properties'])) {
|
|
foreach ($data as $field => $value) {
|
|
if (isset($schema['properties'][$field])) {
|
|
$field_schema = $schema['properties'][$field];
|
|
$this->validateFieldType($value, $field_schema, $field);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate field type against schema
|
|
*/
|
|
private function validateFieldType($value, $field_schema, $field_name)
|
|
{
|
|
if (!isset($field_schema['type'])) {
|
|
return; // No type constraint
|
|
}
|
|
|
|
switch ($field_schema['type']) {
|
|
case 'string':
|
|
$this->assertIsString($value, "Field '{$field_name}' should be string");
|
|
break;
|
|
case 'integer':
|
|
$this->assertIsInt($value, "Field '{$field_name}' should be integer");
|
|
break;
|
|
case 'number':
|
|
$this->assertIsNumeric($value, "Field '{$field_name}' should be numeric");
|
|
break;
|
|
case 'array':
|
|
$this->assertIsArray($value, "Field '{$field_name}' should be array");
|
|
break;
|
|
}
|
|
|
|
// Validate format if specified
|
|
if (isset($field_schema['format'])) {
|
|
switch ($field_schema['format']) {
|
|
case 'date':
|
|
$this->assertMatchesRegularExpression('/^\d{4}-\d{2}-\d{2}$/', $value, "Field '{$field_name}' should be valid date");
|
|
break;
|
|
case 'email':
|
|
$this->assertFilter($value, FILTER_VALIDATE_EMAIL, "Field '{$field_name}' should be valid email");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get endpoint specification from contract
|
|
*/
|
|
private function getEndpointSpec($method, $path)
|
|
{
|
|
$paths = $this->contract_spec['paths'];
|
|
|
|
$this->assertArrayHasKey($path, $paths, "Endpoint '{$path}' not found in contract");
|
|
$this->assertArrayHasKey($method, $paths[$path], "Method '{$method}' not found for endpoint '{$path}'");
|
|
|
|
return $paths[$path][$method];
|
|
}
|
|
|
|
/**
|
|
* Get embedded specification for testing when file is not available
|
|
*/
|
|
private function getEmbeddedSpec()
|
|
{
|
|
return [
|
|
'openapi' => '3.0.3',
|
|
'info' => [
|
|
'title' => 'Moloni API Integration Contract',
|
|
'version' => '3.0.0'
|
|
],
|
|
'servers' => [
|
|
['url' => 'https://api.moloni.pt/v1']
|
|
],
|
|
'paths' => [
|
|
'/oauth2/token' => [
|
|
'post' => [
|
|
'operationId' => 'exchangeToken',
|
|
'requestBody' => [
|
|
'content' => [
|
|
'application/x-www-form-urlencoded' => [
|
|
'schema' => [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'grant_type' => ['type' => 'string'],
|
|
'code' => ['type' => 'string'],
|
|
'client_id' => ['type' => 'string'],
|
|
'client_secret' => ['type' => 'string']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'responses' => [
|
|
'200' => [
|
|
'content' => [
|
|
'application/json' => [
|
|
'schema' => ['$ref' => '#/components/schemas/TokenResponse']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'/customers' => [
|
|
'get' => [
|
|
'operationId' => 'listCustomers',
|
|
'parameters' => [
|
|
[
|
|
'name' => 'company_id',
|
|
'in' => 'query',
|
|
'required' => true,
|
|
'schema' => ['type' => 'integer']
|
|
]
|
|
],
|
|
'responses' => [
|
|
'200' => [
|
|
'content' => [
|
|
'application/json' => [
|
|
'schema' => [
|
|
'type' => 'array',
|
|
'items' => ['$ref' => '#/components/schemas/Customer']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'post' => [
|
|
'operationId' => 'createCustomer',
|
|
'requestBody' => [
|
|
'content' => [
|
|
'application/json' => [
|
|
'schema' => ['$ref' => '#/components/schemas/CustomerCreate']
|
|
]
|
|
]
|
|
],
|
|
'responses' => [
|
|
'201' => [
|
|
'content' => [
|
|
'application/json' => [
|
|
'schema' => ['$ref' => '#/components/schemas/Customer']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
]
|
|
// Additional endpoints would be defined here...
|
|
],
|
|
'components' => [
|
|
'securitySchemes' => [
|
|
'oauth2' => [
|
|
'type' => 'oauth2',
|
|
'flows' => [
|
|
'authorizationCode' => [
|
|
'authorizationUrl' => 'https://api.moloni.pt/v1/oauth2/authorize',
|
|
'tokenUrl' => 'https://api.moloni.pt/v1/oauth2/token'
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'schemas' => [
|
|
'TokenResponse' => [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'access_token' => ['type' => 'string'],
|
|
'token_type' => ['type' => 'string'],
|
|
'expires_in' => ['type' => 'integer'],
|
|
'refresh_token' => ['type' => 'string']
|
|
]
|
|
],
|
|
'Customer' => [
|
|
'type' => 'object',
|
|
'properties' => [
|
|
'customer_id' => ['type' => 'integer'],
|
|
'name' => ['type' => 'string'],
|
|
'vat' => ['type' => 'string'],
|
|
'email' => ['type' => 'string']
|
|
]
|
|
],
|
|
'CustomerCreate' => [
|
|
'type' => 'object',
|
|
'required' => ['company_id', 'name', 'vat'],
|
|
'properties' => [
|
|
'company_id' => ['type' => 'integer'],
|
|
'name' => ['type' => 'string'],
|
|
'vat' => ['type' => 'string'],
|
|
'email' => ['type' => 'string']
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
}
|
|
} |