diff --git a/.php-stan.config.neon b/.php-stan.config.neon index 4daecd6..6dc454c 100644 --- a/.php-stan.config.neon +++ b/.php-stan.config.neon @@ -10,6 +10,7 @@ parameters: identifiers: - argument.type - assign.propertyType + - booleanNot.alwaysFalse - booleanNot.alwaysTrue - callable.nonCallable - method.alreadyNarrowedType diff --git a/packages/cnpj-val/phpunit.xml b/packages/cnpj-val/.pest.config.xml similarity index 82% rename from packages/cnpj-val/phpunit.xml rename to packages/cnpj-val/.pest.config.xml index 90e5fb7..29871d7 100644 --- a/packages/cnpj-val/phpunit.xml +++ b/packages/cnpj-val/.pest.config.xml @@ -3,7 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" - cacheDirectory="vendor/.phpunit.cache/" + cacheDirectory="vendor/.pest.cache/" beStrictAboutOutputDuringTests="true" colors="true" failOnRisky="true" @@ -12,7 +12,7 @@ > - tests/ + tests/ @@ -28,6 +28,6 @@ - + diff --git a/packages/cnpj-val/CHANGELOG.md b/packages/cnpj-val/CHANGELOG.md new file mode 100644 index 0000000..ebe041c --- /dev/null +++ b/packages/cnpj-val/CHANGELOG.md @@ -0,0 +1,59 @@ +# lacus/cnpj-val + +## 2.0.0 + +### 🎉 v2 at a glance 🎊 + +- 🆕 **Alphanumeric CNPJ** — Full support for the new [14-character alphanumeric CNPJ](https://www.gov.br/receitafederal/pt-br/assuntos/noticias/2023/julho/cnpj-alfa-numerico) (digits and letters). +- ⚙️ **`CnpjValidatorOptions`** — Configure `type` (`numeric` vs `alphanumeric`) and `caseSensitive` on the instance or per `isValid()` call. +- 🛡️ **Structured errors** — Typed exceptions (`CnpjValidatorTypeError`, `CnpjValidatorException` and their subclasses) for clearer error handling. + +### BREAKING CHANGES + +- **Namespace migration** — Public API moved from `Lacus\CnpjVal\` to `Lacus\BrUtils\Cnpj\`; update `use` imports for `CnpjValidator`, `CnpjValidatorOptions`, `CnpjValidationType`, and `cnpj_val()`. +- **Drop support to PHP v8.1**: Minimum version for the package is now **PHP 8.2** (`^8.2`). It may even run forcedly in earlier versions, but it's not recommended to keep running stale versions of PHP in production. +- **`CNPJ_LENGTH` constant** — Removed from `cnpj-val.php`; use `CnpjValidatorOptions::CNPJ_LENGTH` instead. +- **Default character set** — Default validation is **alphanumeric** (letters are kept after sanitization). For legacy numeric-only behavior, pass `type: 'numeric'` or `CnpjValidationType::Numeric`. +- **`cnpj_val()` / `isValid()` signatures** — Accept `string|list` and optional `CnpjValidatorOptions` (or named `type` / `caseSensitive`); v1 accepted only a single `string`. +- **Invalid input** — Non-string / non–`string[]` values throw `CnpjValidatorInputTypeError` instead of a native `TypeError`. +- **Check-digit engine** — Validation delegates to `CnpjCheckDigits` from **`lacus/cnpj-dv`** `^1.1` (v1 calculated digits inline via `CnpjGeneratorVerifierDigit` from **`lacus/cnpj-gen`** only). + +### New features + +- **`CnpjValidatorOptions`** — Options object with property access, `getAll()`, `set()`, and `overrides` merging (constructor and per-call). +- **`CnpjValidationType`** — Backed enum (`Alphanumeric`, `Numeric`) for the `type` option. +- **`caseSensitive` option** — When `false`, lowercase letters are uppercased before alphanumeric validation. +- **`getOptions()`** — Returns the shared default `CnpjValidatorOptions` instance used by `CnpjValidator`. +- **Array input** — `isValid()` and `cnpj_val()` concatenate a `list` (e.g. grouped or formatted segments). +- **Verifier-digit rule** — Rejects CNPJs whose last two characters are not digits (`0`–`9`). +- **Exception hierarchy** — `CnpjValidatorInputTypeError`, `CnpjValidatorOptionsTypeError`, `CnpjValidatorOptionTypeInvalidException`, plus `getName()` on base error classes. + +### Improvements + +- **`cnpj_val()` reuse** — The instance of `CnpjValidator` is kept alive and reused across multiple calls. +- **Dependency alignment**: now depends on `lacus/cnpj-dv` and `lacus/utils`. +- **Autoload alignment**: package autoload namespace updated to the BR Utils standard. +- **Docs refresh**: English and pt-BR READMEs updated with v2 API and usage guidance. + +## 1.0.0 + +### Stable v1 API + +First stable release of **`lacus/cnpj-val`** focused on CNPJ checksum validation. + +- **API namespace**: `Lacus\CnpjVal\`. +- **Main resources**: + - `cnpj_val()` + - `CnpjValidator` + - `CNPJ_LENGTH` +- **Validation scope**: + - 14-digit CNPJ with first and second verifier digits checked against the official algorithm. + - Masked or plain input supported by stripping non-digits before validation. +- **Verifier digits**: + - Calculations reuse `CnpjGeneratorVerifierDigit` from **`lacus/cnpj-gen`** `^1.0`. +- **Return model**: + - Ordinary validation failures return `false` (no exceptions for invalid CNPJ). +- **Runtime**: + - PHP `>=8.1`. +- **Testing**: + - PHPUnit-based suite. diff --git a/packages/cnpj-val/README.md b/packages/cnpj-val/README.md index 44d3691..9b58cd7 100644 --- a/packages/cnpj-val/README.md +++ b/packages/cnpj-val/README.md @@ -7,14 +7,28 @@ [![Last Update Date](https://img.shields.io/github/last-commit/LacusSolutions/br-utils-php)](https://github.com/LacusSolutions/br-utils-php) [![Project License](https://img.shields.io/github/license/LacusSolutions/br-utils-php)](https://github.com/LacusSolutions/br-utils-php/blob/main/LICENSE) -Utility function/class to validate CNPJ (Brazilian employer ID). +> 🚀 **Full support for the [new alphanumeric CNPJ format](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).** + +> 🌎 [Acessar documentação em português](https://github.com/LacusSolutions/br-utils-php/blob/main/packages/cnpj-val/README.pt.md) + +A PHP utility to validate CNPJ (Brazilian Business Tax ID) values. ## PHP Support -| ![PHP 8.1](https://img.shields.io/badge/PHP-8.1-777BB4?logo=php&logoColor=white) | ![PHP 8.2](https://img.shields.io/badge/PHP-8.2-777BB4?logo=php&logoColor=white) | ![PHP 8.3](https://img.shields.io/badge/PHP-8.3-777BB4?logo=php&logoColor=white) | ![PHP 8.4](https://img.shields.io/badge/PHP-8.4-777BB4?logo=php&logoColor=white) | -|--- | --- | --- | --- | +| ![PHP 8.2](https://img.shields.io/badge/PHP-8.2-777BB4?logo=php&logoColor=white) | ![PHP 8.3](https://img.shields.io/badge/PHP-8.3-777BB4?logo=php&logoColor=white) | ![PHP 8.4](https://img.shields.io/badge/PHP-8.4-777BB4?logo=php&logoColor=white) | ![PHP 8.5](https://img.shields.io/badge/PHP-8.5-777BB4?logo=php&logoColor=white) | +| --- | --- | --- | --- | | Passing ✔ | Passing ✔ | Passing ✔ | Passing ✔ | +## Features + +- ✅ **Alphanumeric CNPJ**: Validates 14-character CNPJ in numeric or alphanumeric format +- ✅ **Flexible input**: Accepts `string` or `list`; array elements are concatenated in order +- ✅ **Format agnostic**: Strips non-alphanumeric characters (or non-digits when `type` is `numeric`) and optionally uppercases letters +- ✅ **Optional case sensitivity**: When `caseSensitive` is `false`, lowercase letters are accepted for alphanumeric CNPJ +- ✅ **Per-call override model**: Instance defaults can be overridden for one `isValid()` call only +- ✅ **Typed option validation**: Dedicated `TypeError` / `Exception` subclasses for invalid option or input usage +- ✅ **Dual API style**: Object-oriented (`CnpjValidator`) and functional (`cnpj_val()`) + ## Installation ```bash @@ -26,121 +40,166 @@ $ composer require lacus/cnpj-val ```php isValid($cnpj) ? 'Valid' : 'Invalid'; // returns 'Valid' +$validator->isValid('98765432000198'); // true +$validator->isValid('98.765.432/0001-98'); // true +$validator->isValid('98765432000199'); // false -$cnpj = '98.765.432/0001-98'; -echo $validator->isValid($cnpj) ? 'Valid' : 'Invalid'; // returns 'Valid' +$validator->isValid('1QB5UKALPYFP59'); // true (alphanumeric) +$validator->isValid('1QB5UKALpyfp59'); // false (default is case-sensitive) +$validator->isValid('1QB5UKALpyfp59', caseSensitive: false); // true -$cnpj = '98765432000199'; -echo $validator->isValid($cnpj) ? 'Valid' : 'Invalid'; // returns 'Invalid' +$validator->isValid('96206256120884'); // true (numeric) +$validator->isValid('1QB5UKALPYFP59', type: CnpjValidationType::Numeric); // false ``` -### Imperative programming +Functional helper: + +```php +cnpj_val('98765432000198'); // true +cnpj_val('98.765.432/0001-98'); // true +cnpj_val('98765432000199'); // false +``` + +## Usage + +The main entry points are the class `CnpjValidator`, the options value object `CnpjValidatorOptions`, the enum `CnpjValidationType`, and the helper `cnpj_val()`. + +### `CnpjValidator` -The helper function `cnpj_val()` is just a functional abstraction. Internally it creates an instance of `CnpjValidator` and calls the `isValid()` method right away. +- **`__construct`**: `new CnpjValidator(?CnpjValidatorOptions $options = null, $type = null, $caseSensitive = null)` + + If `$options` is a `CnpjValidatorOptions` instance, that same instance is stored internally (mutations later affect future `isValid()` calls with no per-call override). Otherwise, a new options object is built from named values. + +- **`getOptions()`**: Returns the internal `CnpjValidatorOptions` instance. +- **`isValid`**: `isValid(string|list $cnpjInput, ?CnpjValidatorOptions $options = null, $type = null, $caseSensitive = null): bool` + + Per-call options are merged over instance defaults only for that call. Returns `true` when the sanitized input has exactly **14** characters, the last two are digits, and check digits match (`CnpjCheckDigits` from **`lacus/cnpj-dv`**). Otherwise returns `false` (invalid CNPJ, wrong length, ineligible base/branch, etc.) without throwing. + + If the input is not a `string` or a `list` of strings, **`CnpjValidatorInputTypeError`** is thrown. ```php -$cnpj = '98765432000198'; +isValid('98.765.432/0001-98'); // true +$validator->isValid('1QB5UKALPYFP59'); // false (letters stripped → length ≠ 14) +$validator->isValid('1QB5UKALpyfp59', type: CnpjValidationType::Alphanumeric, caseSensitive: false); // true ``` -### Validation Examples +Default options on the instance; per-call overrides: ```php -// Valid CNPJ numbers -cnpj_val('98765432000198') // returns true -cnpj_val('98.765.432/0001-98') // returns true -cnpj_val('03603568000195') // returns true - -// Invalid CNPJ numbers -cnpj_val('98765432000199') // returns false -cnpj_val('12345678901234') // returns false -cnpj_val('00000000000000') // returns false -cnpj_val('11111111111111') // returns false -cnpj_val('123') // returns false (too short) -cnpj_val('') // returns false (empty) +$validator = new CnpjValidator(caseSensitive: false); + +$validator->isValid('1qb5ukalpyfp59'); // true (instance defaults) +$validator->isValid('1qb5ukalpyfp59', caseSensitive: true); // this call only: false +$validator->isValid('1qb5ukalpyfp59'); // true again ``` -## Features +### `CnpjValidatorOptions` -- ✅ **Format Agnostic**: Accepts CNPJ with or without formatting (dots, slashes, dashes) -- ✅ **Strict Validation**: Validates both check digits according to Brazilian CNPJ algorithm -- ✅ **Type Safety**: Built with PHP 8.1+ strict types -- ✅ **Lightweight**: Minimal dependencies, only requires `lacus/cnpj-gen` for check digit calculation -- ✅ **Dual API**: Both object-oriented and functional programming styles supported +Holds validator settings (`type`, `caseSensitive`). Construct with named parameters and optional `overrides` (list of arrays and/or other `CnpjValidatorOptions` instances, merged in order). Exposes properties via magic `__get` / `__set`. -## API Reference +- **`getAll()`**: Returns a shallow array snapshot of all options. +- **`set(...)`**: Updates multiple fields at once; returns `$this`. -### CnpjValidator Class +### `CnpjValidationType` -#### `isValid(string $cnpjString): bool` +Backed enum for the `type` option: -Validates a CNPJ string and returns `true` if valid, `false` otherwise. +- `CnpjValidationType::Alphanumeric` (`"alphanumeric"`) — default; keeps `0`–`9` and `A`–`Z` after sanitization. +- `CnpjValidationType::Numeric` (`"numeric"`) — legacy numeric-only CNPJ; strips everything except `0`–`9`. -**Parameters:** -- `$cnpjString` (string): The CNPJ to validate (with or without formatting) +Helper methods: -**Returns:** -- `bool`: `true` if the CNPJ is valid, `false` otherwise +- `CnpjValidationType::values(): list` +- `toSequenceType(): SequenceType` (from **`lacus/utils`**) -### cnpj_val() Function +String literals `'alphanumeric'` and `'numeric'` are also accepted wherever `type` is documented. -#### `cnpj_val(string $cnpjString): bool` +### Functional helper -Functional wrapper around `CnpjValidator::isValid()`. +`cnpj_val()` runs the validation on a `CnpjValidator` instance with the same arguments passed to the function. Use named arguments for options: -**Parameters:** -- `$cnpjString` (string): The CNPJ to validate (with or without formatting) +```php +cnpj_val('98765432000198'); // true +cnpj_val('1QB5UKALpyfp59', caseSensitive: false); // true +cnpj_val('1QB5UKALPYFP59', type: CnpjValidationType::Numeric); // false +``` -**Returns:** -- `bool`: `true` if the CNPJ is valid, `false` otherwise +To pass a full options object as the second argument: `cnpj_val($cnpj, new CnpjValidatorOptions(type: CnpjValidationType::Numeric))`. -## Validation Algorithm +### Input formats -The package validates CNPJ using the official Brazilian algorithm: +**String:** Raw digits and/or letters, or formatted CNPJ (e.g. `98.765.432/0001-98`, `1Q.B5U.KAL/PYFP-59`). Characters are stripped according to `type`; when `caseSensitive` is `false`, letters are uppercased before alphanumeric validation. -1. **Length Check**: Ensures the CNPJ has exactly 14 digits -2. **First Check Digit**: Calculates and validates the 13th digit -3. **Second Check Digit**: Calculates and validates the 14th digit -4. **Format Tolerance**: Automatically strips non-numeric characters before validation +**Array of strings:** Each element must be a string; values are concatenated (e.g. per digit, grouped segments, or mixed with punctuation). Non-string elements throw **`CnpjValidatorInputTypeError`**. -## Error Handling +### Validation options -The validator is designed to be forgiving with input format but strict with validation: +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `type` | `CnpjValidationType\|'alphanumeric'\|'numeric'\|null` | `CnpjValidationType::Alphanumeric` | Character set after sanitization: alphanumeric (`0`–`9`, `A`–`Z`) or numeric-only (`0`–`9`) | +| `caseSensitive` | `?bool` | `true` | When `false`, lowercase letters are uppercased before alphanumeric validation | -- Invalid formats (too short, too long) return `false` -- Invalid check digits return `false` -- Empty strings return `false` -- Non-numeric strings (after stripping formatting) return `false` +Invalid CNPJ (wrong length after sanitization, invalid check digits, ineligible base/branch `00000000` / `0000`, repeated digits, non-numeric verifier digits) returns **`false`** — no exception is thrown for validation failure. + +### Errors & exceptions + +This package uses **TypeError** for invalid option/input types and **Exception** for invalid option values. Validation failures return `false`. + +- **Wrong input type** (not `string` or `list`): **`CnpjValidatorInputTypeError`** — extends **`CnpjValidatorTypeError`** (extends PHP `TypeError`). +- **Invalid option types when constructing or merging options**: **`CnpjValidatorOptionsTypeError`**. +- **Invalid `type` value** (not `alphanumeric` / `numeric`): **`CnpjValidatorOptionTypeInvalidException`** — extends **`CnpjValidatorException`**. + +```php +isValid(12345678000198); +} catch (CnpjValidatorInputTypeError $e) { + echo $e->getMessage(); +} + +try { + new CnpjValidator(type: 'invalid'); +} catch (CnpjValidatorOptionTypeInvalidException $e) { + echo $e->getMessage(); +} +``` -## Dependencies +### Other available resources -- **PHP**: >= 8.1 -- **lacus/cnpj-gen**: ^1.0 (for check digit calculation) +- **`CnpjValidatorOptions::CNPJ_LENGTH`**: `14` — standard CNPJ length after sanitization. ## Contribution & Support -We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-php/blob/main/CONTRIBUTING.md) for details. But if you find this project helpful, please consider: +We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-php/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider: - ⭐ Starring the repository - 🤝 Contributing to the codebase @@ -149,7 +208,7 @@ We welcome contributions! Please see our [Contributing Guidelines](https://githu ## License -This project is licensed under the MIT License - see the [LICENSE](https://github.com/LacusSolutions/br-utils-php/blob/main/LICENSE) file for details. +This project is licensed under the MIT License — see the [LICENSE](https://github.com/LacusSolutions/br-utils-php/blob/main/LICENSE) file for details. ## Changelog diff --git a/packages/cnpj-val/README.pt.md b/packages/cnpj-val/README.pt.md new file mode 100644 index 0000000..60f2bac --- /dev/null +++ b/packages/cnpj-val/README.pt.md @@ -0,0 +1,206 @@ +![cnpj-val para PHP](https://br-utils.vercel.app/img/cover_cnpj-val.jpg) + +> 🚀 **Suporte total ao [novo formato alfanumérico de CNPJ](https://github.com/user-attachments/files/23937961/calculodvcnpjalfanaumerico.pdf).** + +> 🌎 [Access documentation in English](https://github.com/LacusSolutions/br-utils-php/blob/main/packages/cnpj-val/README.md) + +Utilitário em PHP para validar CNPJs (Cadastro Nacional da Pessoa Jurídica). + +## Recursos + +- ✅ **CNPJ alfanumérico**: Valida CNPJ de 14 caracteres no formato numérico ou alfanumérico +- ✅ **Entrada flexível**: Aceita `string` ou `list`; elementos do array são concatenados na ordem +- ✅ **Agnóstico ao formato**: Remove caracteres não alfanuméricos (ou não numéricos quando `type` é `numeric`) e opcionalmente converte letras para maiúsculas +- ✅ **Sensibilidade a maiúsculas opcional**: Com `caseSensitive` em `false`, letras minúsculas são aceitas para CNPJ alfanumérico +- ✅ **Sobrescrita por chamada**: Os padrões da instância podem ser sobrescritos em uma única chamada de `isValid()` +- ✅ **Validação tipada de opções**: Subclasses específicas de `TypeError` / `Exception` para uso inválido de opções ou entrada +- ✅ **Duas formas de uso**: Orientada a objeto (`CnpjValidator`) e funcional (`cnpj_val()`) + +## Instalação + +```bash +# usando Composer +$ composer require lacus/cnpj-val +``` + +## Importação + +```php +isValid('98765432000198'); // true +$validator->isValid('98.765.432/0001-98'); // true +$validator->isValid('98765432000199'); // false + +$validator->isValid('1QB5UKALPYFP59'); // true (alfanumérico) +$validator->isValid('1QB5UKALpyfp59'); // false (padrão é case-sensitive) +$validator->isValid('1QB5UKALpyfp59', caseSensitive: false); // true + +$validator->isValid('96206256120884'); // true (numérico) +$validator->isValid('1QB5UKALPYFP59', type: CnpjValidationType::Numeric); // false +``` + +Helper funcional: + +```php +cnpj_val('98765432000198'); // true +cnpj_val('98.765.432/0001-98'); // true +cnpj_val('98765432000199'); // false +``` + +## Utilização + +Os principais recursos são a classe `CnpjValidator`, o objeto de valor `CnpjValidatorOptions`, o enum `CnpjValidationType` e o helper `cnpj_val()`. + +### `CnpjValidator` + +- **`__construct`**: `new CnpjValidator(?CnpjValidatorOptions $options = null, $type = null, $caseSensitive = null)` + + Se `$options` for uma instância de `CnpjValidatorOptions`, essa mesma instância é armazenada internamente (mutações posteriores afetam futuras chamadas de `isValid()` sem sobrescrita por chamada). Caso contrário, um novo objeto de opções é construído a partir dos valores nomeados. + +- **`getOptions()`**: Retorna a instância interna de `CnpjValidatorOptions`. +- **`isValid`**: `isValid(string|list $cnpjInput, ?CnpjValidatorOptions $options = null, $type = null, $caseSensitive = null): bool` + + As opções por chamada são mescladas sobre os padrões da instância apenas naquela chamada. Retorna `true` quando a entrada sanitizada tem exatamente **14** caracteres, os dois últimos são dígitos e os dígitos verificadores conferem (`CnpjCheckDigits` de **`lacus/cnpj-dv`**). Caso contrário retorna `false` (CNPJ inválido, tamanho incorreto, base/filial inelegíveis, etc.) sem lançar exceção. + + Se a entrada não for `string` nem `list` de strings, é lançada **`CnpjValidatorInputTypeError`**. + +```php +isValid('98.765.432/0001-98'); // true +$validator->isValid('1QB5UKALPYFP59'); // false (letras removidas → tamanho ≠ 14) +$validator->isValid('1QB5UKALpyfp59', type: CnpjValidationType::Alphanumeric, caseSensitive: false); // true +``` + +Opções padrão na instância; sobrescrita por chamada: + +```php +$validator = new CnpjValidator(caseSensitive: false); + +$validator->isValid('1qb5ukalpyfp59'); // true (padrões da instância) +$validator->isValid('1qb5ukalpyfp59', caseSensitive: true); // só nesta chamada: false +$validator->isValid('1qb5ukalpyfp59'); // true de novo +``` + +### `CnpjValidatorOptions` + +Armazena as configurações do validador (`type`, `caseSensitive`). Construa com parâmetros nomeados e `overrides` opcional (lista de arrays e/ou outras instâncias de `CnpjValidatorOptions`, mescladas em ordem). Expõe propriedades via `__get` / `__set` mágicos. + +- **`getAll()`**: Retorna um array superficial com todas as opções. +- **`set(...)`**: Atualiza vários campos de uma vez; retorna `$this`. + +### `CnpjValidationType` + +Enum com valor de apoio para a opção `type`: + +- `CnpjValidationType::Alphanumeric` (`"alphanumeric"`) — padrão; mantém `0`–`9` e `A`–`Z` após sanitização. +- `CnpjValidationType::Numeric` (`"numeric"`) — CNPJ numérico legado; remove tudo exceto `0`–`9`. + +Métodos auxiliares: + +- `CnpjValidationType::values(): list` +- `toSequenceType(): SequenceType` (de **`lacus/utils`**) + +Os literais `'alphanumeric'` e `'numeric'` também são aceitos onde `type` é documentado. + +### Helper funcional + +`cnpj_val()` executa a validação em uma instância de `CnpjValidator` com os mesmos argumentos passados à função. Use argumentos nomeados para opções: + +```php +cnpj_val('98765432000198'); // true +cnpj_val('1QB5UKALpyfp59', caseSensitive: false); // true +cnpj_val('1QB5UKALPYFP59', type: CnpjValidationType::Numeric); // false +``` + +Para passar um objeto de opções completo como segundo argumento: `cnpj_val($cnpj, new CnpjValidatorOptions(type: CnpjValidationType::Numeric))`. + +### Formatos de entrada + +**String:** Dígitos e/ou letras brutos, ou CNPJ já formatado (ex.: `98.765.432/0001-98`, `1Q.B5U.KAL/PYFP-59`). Caracteres são removidos conforme `type`; com `caseSensitive` em `false`, letras são convertidas para maiúsculas antes da validação alfanumérica. + +**Array de strings:** Cada elemento deve ser string; os valores são concatenados (ex.: por dígito, segmentos agrupados ou misturados com pontuação). Elementos não string lançam **`CnpjValidatorInputTypeError`**. + +### Opções de validação + +| Parâmetro | Tipo | Padrão | Descrição | +|-----------|------|---------|-------------| +| `type` | `CnpjValidationType\|'alphanumeric'\|'numeric'\|null` | `CnpjValidationType::Alphanumeric` | Conjunto de caracteres após sanitização: alfanumérico (`0`–`9`, `A`–`Z`) ou somente numérico (`0`–`9`) | +| `caseSensitive` | `?bool` | `true` | Se `false`, letras minúsculas são convertidas para maiúsculas antes da validação alfanumérica | + +CNPJ inválido (tamanho incorreto após sanitização, dígitos verificadores inválidos, base/filial inelegíveis `00000000` / `0000`, dígitos repetidos, verificadores não numéricos) retorna **`false`** — falhas de validação não lançam exceção. + +### Erros e exceções + +O pacote usa **TypeError** para tipos de opção/entrada inválidos e **Exception** para valores de opção inválidos. Falhas de validação retornam `false`. + +- **Tipo de entrada incorreto** (não `string` nem `list`): **`CnpjValidatorInputTypeError`** — estende **`CnpjValidatorTypeError`** (estende `TypeError` do PHP). +- **Tipos de opção inválidos** ao construir ou mesclar opções: **`CnpjValidatorOptionsTypeError`**. +- **Valor de `type` inválido** (não `alphanumeric` / `numeric`): **`CnpjValidatorOptionTypeInvalidException`** — estende **`CnpjValidatorException`**. + +```php +isValid(12345678000198); +} catch (CnpjValidatorInputTypeError $e) { + echo $e->getMessage(); +} + +try { + new CnpjValidator(type: 'invalid'); +} catch (CnpjValidatorOptionTypeInvalidException $e) { + echo $e->getMessage(); +} +``` + +### Outros recursos disponíveis + +- **`CnpjValidatorOptions::CNPJ_LENGTH`**: `14` — comprimento padrão do CNPJ após sanitização. + +## Contribuição e suporte + +Contribuições são bem-vindas! Consulte as [Diretrizes de contribuição](https://github.com/LacusSolutions/br-utils-php/blob/main/CONTRIBUTING.md). Se o projeto for útil para você, considere: + +- ⭐ Dar uma estrela no repositório +- 🤝 Contribuir com código +- 💡 [Sugerir novas funcionalidades](https://github.com/LacusSolutions/br-utils-php/issues) +- 🐛 [Reportar bugs](https://github.com/LacusSolutions/br-utils-php/issues) + +## Licença + +Este projeto está sob a licença MIT — veja o arquivo [LICENSE](https://github.com/LacusSolutions/br-utils-php/blob/main/LICENSE). + +## Changelog + +Veja o [CHANGELOG](https://github.com/LacusSolutions/br-utils-php/blob/main/packages/cnpj-val/CHANGELOG.md) para alterações e histórico de versões. + +--- + +Feito com ❤️ por [Lacus Solutions](https://github.com/LacusSolutions) diff --git a/packages/cnpj-val/composer.json b/packages/cnpj-val/composer.json index 4e5eb68..faf4a81 100644 --- a/packages/cnpj-val/composer.json +++ b/packages/cnpj-val/composer.json @@ -1,7 +1,7 @@ { "name": "lacus/cnpj-val", "type": "library", - "description": "Utility function to validate CNPJ (Brazilian employer ID)", + "description": "Utility to validate CNPJ (Brazilian Business Tax ID)", "license": "MIT", "authors": [ { @@ -36,26 +36,34 @@ ], "lint:format": "php-cs-fixer fix --config=.php-cs-fixer.config.php", "lint:check": "phpstan analyse --configuration=.php-stan.config.neon", - "test": "phpunit", - "test:watch": "phpunit-watcher watch", - "test-coverage": "phpunit --coverage-html coverage" + "test": [ + "pest --configuration=.pest.config.xml --group isolated-process-tests", + "pest --configuration=.pest.config.xml --exclude-group isolated-process-tests" + ], + "test:cov": [ + "pest --configuration=.pest.config.xml --group isolated-process-tests", + "pest --configuration=.pest.config.xml --exclude-group isolated-process-tests --coverage-html coverage" + ] }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "pestphp/pest-plugin": true + } }, "require": { - "php": ">=8.1", - "lacus/cnpj-gen": "^1.0" + "php": "^8.2", + "lacus/cnpj-dv": "^1.1", + "lacus/utils": "^1.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.94", - "phpstan/phpstan": "^2.1", - "phpunit/phpunit": "^10.5", - "spatie/phpunit-watcher": "~1.24" + "pestphp/pest": "^3.8", + "phpstan/phpstan": "^2.1" }, "autoload": { "psr-4": { - "Lacus\\CnpjVal\\": "src/" + "Lacus\\BrUtils\\Cnpj\\": "src/" }, "files": [ "src/cnpj-val.php" @@ -63,7 +71,7 @@ }, "autoload-dev": { "psr-4": { - "Lacus\\CnpjVal\\Tests\\": "tests/" + "Lacus\\BrUtils\\Cnpj\\Tests\\": "tests/" } } } diff --git a/packages/cnpj-val/src/CnpjValidator.php b/packages/cnpj-val/src/CnpjValidator.php index f047961..9571ba9 100644 --- a/packages/cnpj-val/src/CnpjValidator.php +++ b/packages/cnpj-val/src/CnpjValidator.php @@ -2,47 +2,190 @@ declare(strict_types=1); -namespace Lacus\CnpjVal; +namespace Lacus\BrUtils\Cnpj; -use Lacus\CnpjGen\CnpjGeneratorVerifierDigit; +use Lacus\BrUtils\Cnpj\Enums\CnpjValidationType; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorInputTypeError; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorOptionsTypeError; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorOptionTypeInvalidException; +use Throwable; +/** + * Validator for CNPJ (Cadastro Nacional da Pessoa Jurídica) identifiers. + * Validates CNPJ strings according to the Brazilian CNPJ validation algorithm. + */ class CnpjValidator { - private CnpjGeneratorVerifierDigit $verifierDigit; + /** + * The default options used by this validator instance. + */ + private readonly CnpjValidatorOptions $options; - public function __construct() - { - $this->verifierDigit = new CnpjGeneratorVerifierDigit(); + /** + * Creates a new `CnpjValidator` with optional default options. + * + * Default options apply to every call to `isValid` unless overridden by + * per-call argument. Options control case sensitivity and the type of + * whether the CNPJ input is alphanumeric or numeric. + * + * When `defaultOptions` is a `CnpjValidatorOptions` instance, that + * instance is used directly (no copy is created). Mutating it later (e.g. + * via the `options` getter or the original reference) affects future + * `isValid` calls that do not pass per-call options. + * + * When named parameters or none are passed, a new `CnpjValidatorOptions` + * instance is created from it. + * + * @param ?CnpjValidatorOptions $options + * @param ?(CnpjValidationType|'alphanumeric'|'numeric') $type + * @param ?bool $caseSensitive + * + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is not one of the allowed values. + */ + public function __construct( + ?CnpjValidatorOptions $options = null, + $type = null, + $caseSensitive = null, + ) { + $this->options = $options instanceof CnpjValidatorOptions + ? $options + : new CnpjValidatorOptions( + caseSensitive: $caseSensitive, + type: $type, + overrides: [$options], + ); } - public function isValid(string $cnpjString): bool + /** + * Returns the default options used by this validator when per-call options + * are not provided. + * + * The returned object is the same instance used internally; mutating it (e.g. + * via setters on `CnpjValidatorOptions`) affects future `isValid` calls that + * do not pass `options`. + */ + public function getOptions(): CnpjValidatorOptions { - $cnpjNumbersString = preg_replace('/[^0-9]/', '', $cnpjString) ?? ''; - $cnpjNumbersStringArray = str_split($cnpjNumbersString); - $cnpjNumbersArray = array_map('intval', $cnpjNumbersStringArray); + return $this->options; + } - if (count($cnpjNumbersArray) !== CNPJ_LENGTH) { - return false; + /** + * Validates a CNPJ input. + * + * Per-call `options` are merged over the default options instance for this + * call alone. The default options instance remains unchanged. + * + * @param string|list $cnpjInput + * @param ?CnpjValidatorOptions $options + * @param ?(CnpjValidationType|'alphanumeric'|'numeric') $type + * @param ?bool $caseSensitive + * + * @throws CnpjValidatorInputTypeError If the input is not a string or an + * array of strings. + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is not one of the allowed values. + */ + public function isValid( + $cnpjInput, + $options = null, + $type = null, + $caseSensitive = null, + ): bool { + $actualInput = $this->toStringInput($cnpjInput); + $actualOptions = $this->resolveOptions($options, $type, $caseSensitive); + + $sanitizedCnpj = $actualInput; + + if (!$actualOptions->caseSensitive) { + $sanitizedCnpj = strtoupper($sanitizedCnpj); } - $firstVerifierDigitIndex = CNPJ_LENGTH - 2; - $providedFirstVerifierDigit = $cnpjNumbersArray[$firstVerifierDigitIndex]; - $baseFirstVerifierDigitCalculation = array_slice($cnpjNumbersArray, 0, $firstVerifierDigitIndex); - $calculatedFirstVerifierDigit = $this->verifierDigit->calculate($baseFirstVerifierDigitCalculation); + if ($actualOptions->type === CnpjValidationType::Numeric) { + $sanitizedCnpj = preg_replace('/[^0-9]/', '', $sanitizedCnpj) ?? ''; + } else { + $sanitizedCnpj = preg_replace('/[^0-9A-Z]/i', '', $sanitizedCnpj) ?? ''; + } - if ($providedFirstVerifierDigit !== $calculatedFirstVerifierDigit) { + if (strlen($sanitizedCnpj) !== CnpjValidatorOptions::CNPJ_LENGTH) { return false; } - $secondVerifierDigitIndex = CNPJ_LENGTH - 1; - $baseSecondVerifierDigitCalculation = array_slice($cnpjNumbersArray, 0, $secondVerifierDigitIndex); - $providedSecondVerifierDigit = $cnpjNumbersArray[$secondVerifierDigitIndex]; - $calculatedSecondVerifierDigit = $this->verifierDigit->calculate($baseSecondVerifierDigitCalculation); + if ( + $sanitizedCnpj[12] < '0' + || $sanitizedCnpj[12] > '9' + || $sanitizedCnpj[13] < '0' + || $sanitizedCnpj[13] > '9' + ) { + return false; + } + + try { + $cnpjCheckDigits = new CnpjCheckDigits($sanitizedCnpj); - if ($providedSecondVerifierDigit !== $calculatedSecondVerifierDigit) { + return $sanitizedCnpj === $cnpjCheckDigits->cnpj; + } catch (Throwable $e) { return false; } + } + + /** + * Normalizes the input to a string. + * + * @throws CnpjValidatorInputTypeError If the input is not a string or an + * array of strings. + */ + private function toStringInput(mixed $cnpjInput): string + { + if (is_string($cnpjInput)) { + return $cnpjInput; + } + + if (is_array($cnpjInput)) { + $parts = []; + + foreach ($cnpjInput as $item) { + if (!is_string($item)) { + throw new CnpjValidatorInputTypeError($cnpjInput, 'string or string[]'); + } + + $parts[] = $item; + } + + return implode('', $parts); + } + + throw new CnpjValidatorInputTypeError($cnpjInput, 'string or string[]'); + } + + /** + * Merges per-call options over instance defaults when any override is present. + * + * @param ?CnpjValidatorOptions $options + * @param ?(CnpjValidationType|'alphanumeric'|'numeric') $type + * @param ?bool $caseSensitive + * + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is not one of the allowed values. + */ + private function resolveOptions( + $options = null, + $type = null, + $caseSensitive = null, + ): CnpjValidatorOptions { + if ($options === null && $type === null && $caseSensitive === null) { + return $this->options; + } - return true; + return new CnpjValidatorOptions( + ...$this->options->getAll(), + overrides: [ + [ + 'type' => $type, + 'caseSensitive' => $caseSensitive, + ], + $options ?? [], + ], + ); } } diff --git a/packages/cnpj-val/src/CnpjValidatorOptions.php b/packages/cnpj-val/src/CnpjValidatorOptions.php new file mode 100644 index 0000000..054a8a8 --- /dev/null +++ b/packages/cnpj-val/src/CnpjValidatorOptions.php @@ -0,0 +1,237 @@ + $overrides + * + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is + * not one of the allowed values. + */ + public function __construct( + $type = null, + $caseSensitive = null, + ?array $overrides = [], + ) { + $this->setType($type); + $this->setCaseSensitive($caseSensitive); + + foreach (($overrides ?? []) as $override) { + if ($override === null) { + continue; + } + + if ($override instanceof CnpjValidatorOptions) { + $this->set(...$override->getAll()); + } elseif (is_array($override)) { + $this->set( + type: $override['type'] ?? null, + caseSensitive: $override['caseSensitive'] ?? null, + ); + } + } + } + + /** + * Property-style access to the options. + */ + public function __get(string $name): mixed + { + return match ($name) { + 'type' => $this->getType(), + 'caseSensitive' => $this->getCaseSensitive(), + default => throw new InvalidArgumentException("Unknown property: {$name}"), + }; + } + + /** + * Property-style mutation to the options. + */ + public function __set(string $name, mixed $value): void + { + match ($name) { + 'type' => $this->setType($value), // @phpstan-ignore-line argument.type + 'caseSensitive' => $this->setCaseSensitive($value), // @phpstan-ignore-line argument.type + default => throw new InvalidArgumentException("Unknown property: {$name}"), + }; + } + + /** + * Returns a shallow copy of all current options. This is useful for + * creating snapshots of the current configuration. + * + * @return array{ + * type: CnpjValidationType, + * caseSensitive: bool, + * } + */ + public function getAll(): array + { + return [...$this->options]; + } + + /** + * Gets the type of characters to validate for the CNPJ. + */ + private function getType(): CnpjValidationType + { + return $this->options['type']; + } + + /** + * Sets the type of characters to validate for the CNPJ. + * + * The options are: + * - `alphanumeric`: alphanumeric CNPJ format. + * - `numeric`: numeric-only (legacy) CNPJ format. + * + * @param CnpjValidationType|'alphanumeric'|'numeric'|null $value + * + * @throws CnpjValidatorOptionsTypeError If the value is not a string. + * @throws CnpjValidatorOptionTypeInvalidException If the value is not a + * valid type. + */ + private function setType($value): void + { + $actualType = $value ?? self::DEFAULT_TYPE; + $actualType = $this->parseCnpjValidationType('type', $actualType); + + $this->options['type'] = $actualType; + } + + /** + * Gets whether the CNPJ is validated in a case-sensitive manner. + */ + private function getCaseSensitive(): bool + { + return $this->options['caseSensitive']; + } + + /** + * Sets whether the CNPJ is validated in a case-sensitive manner. + * + * @param bool|null $value + */ + private function setCaseSensitive($value): void + { + $actualCaseSensitive = $value ?? self::DEFAULT_CASE_SENSITIVE; + $actualCaseSensitive = (bool) $actualCaseSensitive; + + $this->options['caseSensitive'] = $actualCaseSensitive; + } + + /** + * Sets multiple options at once. This method allows you to update multiple + * options in a single call. Only the non-nullable provided options are + * updated; options not included in the object or set to `null` retain + * their current values. + * + * @param ?(CnpjValidationType|'alphanumeric'|'numeric') $type + * @param ?bool $caseSensitive + * + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is + * not one of the allowed values. + */ + public function set( + $type = null, + $caseSensitive = null, + ): self { + $this->setType($type ?? $this->getType()); + $this->setCaseSensitive($caseSensitive ?? $this->getCaseSensitive()); + + return $this; + } + + /** + * Throws if the given value is not a CnpjValidationType. + * + * @param 'type' $optionName + * @param CnpjValidationType|string $value + * + * @throws CnpjValidatorOptionsTypeError If the value is not a string. + * @throws CnpjValidatorOptionTypeInvalidException If the value is not a + * valid type. + */ + private function parseCnpjValidationType(string $optionName, mixed $value): CnpjValidationType + { + if ($value instanceof CnpjValidationType) { + return $value; + } + + if (is_string($value)) { + $cnpjValidationType = CnpjValidationType::tryFrom($value); + + if ($cnpjValidationType) { + return $cnpjValidationType; + } + + throw new CnpjValidatorOptionTypeInvalidException($value, CnpjValidationType::values()); + } + + throw new CnpjValidatorOptionsTypeError($optionName, $value, 'CnpjValidationType or string'); + } +} diff --git a/packages/cnpj-val/src/Enums/CnpjValidationType.php b/packages/cnpj-val/src/Enums/CnpjValidationType.php new file mode 100644 index 0000000..bd85ee5 --- /dev/null +++ b/packages/cnpj-val/src/Enums/CnpjValidationType.php @@ -0,0 +1,45 @@ +value; + case Numeric = SequenceType::Numeric->value; + + /** + * Get the values of the CNPJ validation type. + * + * @return list + */ + public static function values(): array + { + $cases = self::cases(); + $values = array_map(fn (CnpjValidationType $case) => $case->value, $cases); + + return $values; + } + + /** + * Convert the CNPJ validation type to a SequenceType. + * + * @return SequenceType + */ + public function toSequenceType(): SequenceType + { + return match ($this) { + self::Alphanumeric => SequenceType::Alphanumeric, + self::Numeric => SequenceType::Numeric, + }; + } +} diff --git a/packages/cnpj-val/src/Exceptions/CnpjValidatorException.php b/packages/cnpj-val/src/Exceptions/CnpjValidatorException.php new file mode 100644 index 0000000..456bee3 --- /dev/null +++ b/packages/cnpj-val/src/Exceptions/CnpjValidatorException.php @@ -0,0 +1,38 @@ + */ + public readonly array $expectedValues; + + /** + * @param list $expectedValues + */ + public function __construct(string $actualInput, array $expectedValues) + { + $expectedValuesString = implode('", "', $expectedValues); + + parent::__construct("CNPJ validator option \"type\" accepts only the following values: \"{$expectedValuesString}\". Got \"{$actualInput}\"."); + $this->actualInput = $actualInput; + $this->expectedValues = $expectedValues; + } +} diff --git a/packages/cnpj-val/src/Exceptions/CnpjValidatorOptionsTypeError.php b/packages/cnpj-val/src/Exceptions/CnpjValidatorOptionsTypeError.php new file mode 100644 index 0000000..4519a11 --- /dev/null +++ b/packages/cnpj-val/src/Exceptions/CnpjValidatorOptionsTypeError.php @@ -0,0 +1,33 @@ +optionName = $optionName; + } +} diff --git a/packages/cnpj-val/src/Exceptions/CnpjValidatorTypeError.php b/packages/cnpj-val/src/Exceptions/CnpjValidatorTypeError.php new file mode 100644 index 0000000..202418d --- /dev/null +++ b/packages/cnpj-val/src/Exceptions/CnpjValidatorTypeError.php @@ -0,0 +1,47 @@ +actualInput = $actualInput; + $this->actualType = $actualType; + $this->expectedType = $expectedType; + } + + /** + * Get the short class name of the error instance. + */ + public function getName(): string + { + $className = static::class; + $lastBackslashIndex = strrpos($className, '\\'); + + if ($lastBackslashIndex === false) { + return $className; + } + + return substr($className, $lastBackslashIndex + 1); + } +} diff --git a/packages/cnpj-val/src/cnpj-val.php b/packages/cnpj-val/src/cnpj-val.php index bff7d50..d1632a2 100644 --- a/packages/cnpj-val/src/cnpj-val.php +++ b/packages/cnpj-val/src/cnpj-val.php @@ -2,13 +2,41 @@ declare(strict_types=1); -namespace Lacus\CnpjVal; +namespace Lacus\BrUtils\Cnpj; -const CNPJ_LENGTH = 14; +use Lacus\BrUtils\Cnpj\Enums\CnpjValidationType; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorInputTypeError; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorOptionsTypeError; +use Lacus\BrUtils\Cnpj\Exceptions\CnpjValidatorOptionTypeInvalidException; -function cnpj_val(string $cnpjString): bool -{ - $formatter = new CnpjValidator(); +/** + * Helper function to simplify the usage of the {@link CnpjValidator} class. + * + * If no options are provided, it validates a CNPJ string or array of strings + * using default settings. If options are provided, they control case + * sensitivity and the type of characters to be validated. + * + * @param string|list $cnpjInput + * @param ?CnpjValidatorOptions $options + * @param ?(CnpjValidationType|'alphanumeric'|'numeric') $type + * @param ?bool $caseSensitive + * + * @throws CnpjValidatorInputTypeError If the input is not a string or an + * array of strings. + * @throws CnpjValidatorOptionsTypeError If any option has an invalid type. + * @throws CnpjValidatorOptionTypeInvalidException If the `type` option is not one of the allowed values. + */ +function cnpj_val( + $cnpjInput, + $options = null, + $type = null, + $caseSensitive = null, +): bool { + static $defaultValidator = null; - return $formatter->isValid($cnpjString); + if (!$defaultValidator instanceof CnpjValidator) { + $defaultValidator = new CnpjValidator(); + } + + return $defaultValidator->isValid($cnpjInput, $options, $type, $caseSensitive); } diff --git a/packages/cnpj-val/tests/CnpjValidatorClassTest.php b/packages/cnpj-val/tests/CnpjValidatorClassTest.php deleted file mode 100644 index fb3cb89..0000000 --- a/packages/cnpj-val/tests/CnpjValidatorClassTest.php +++ /dev/null @@ -1,25 +0,0 @@ -validator = new CnpjValidator(); - } - - protected function isValid(string $cnpjString): bool - { - return $this->validator->isValid($cnpjString); - } -} diff --git a/packages/cnpj-val/tests/CnpjValidatorFunctionTest.php b/packages/cnpj-val/tests/CnpjValidatorFunctionTest.php deleted file mode 100644 index bc21518..0000000 --- a/packages/cnpj-val/tests/CnpjValidatorFunctionTest.php +++ /dev/null @@ -1,19 +0,0 @@ -isValid('22.250.620/0001-11'); - - $this->assertTrue($result); - } - - public function testCnpjStringWithDotsAndDotIsValid(): void - { - $result = $this->isValid('53.975.985/0001.37'); - - $this->assertTrue($result); - } - - public function testCnpjStringWithUnderscoresAndPipeIsValid(): void - { - $result = $this->isValid('31_592_118|0001_80'); - - $this->assertTrue($result); - } - - public function testCnpjStringWithDashIsValid(): void - { - $result = $this->isValid('188549330001-01'); - - $this->assertTrue($result); - } - - public function testCnpjStringOnlyNumbersIsValid(): void - { - $result = $this->isValid('19593887000105'); - - $this->assertTrue($result); - } - - public function testCnpjString99042801000187IsValid(): void - { - $result = $this->isValid('99042801000187'); - - $this->assertTrue($result); - } - - public function testCnpjString27728000000169IsValid(): void - { - $result = $this->isValid('27728000000169'); - - $this->assertTrue($result); - } - - public function testCnpjString72199088000123IsValid(): void - { - $result = $this->isValid('72199088000123'); - - $this->assertTrue($result); - } - - public function testCnpjString00113719000139IsValid(): void - { - $result = $this->isValid('00113719000139'); - - $this->assertTrue($result); - } - - public function testCnpjString50096743000185IsValid(): void - { - $result = $this->isValid('50096743000185'); - - $this->assertTrue($result); - } - - // Invalid CNPJ test cases - public function testCnpjStringWithDotsAndDashIsNotValid(): void - { - $result = $this->isValid('68.224.994/0001-62'); - - $this->assertFalse($result); - } - - public function testCnpjStringWithDotsAndPipeIsNotValid(): void - { - $result = $this->isValid('41.406.219|0001.73'); - - $this->assertFalse($result); - } - - public function testCnpjStringWithUnderscoresAndHashIsNotValid(): void - { - $result = $this->isValid('46_063_859#0001_41'); - - $this->assertFalse($result); - } - - public function testCnpjStringWithSlashIsNotValid(): void - { - $result = $this->isValid('54964126/000106'); - - $this->assertFalse($result); - } - - public function testCnpjString03783943000127IsNotValid(): void - { - $result = $this->isValid('03783943000127'); - - $this->assertFalse($result); - } - - // Other random values are invalid - public function testValue123IsNotValid(): void - { - $result = $this->isValid('123'); - - $this->assertFalse($result); - } - - public function testValue123456IsNotValid(): void - { - $result = $this->isValid('123456'); - - $this->assertFalse($result); - } - - public function testValue123456789IsNotValid(): void - { - $result = $this->isValid('123456789'); - - $this->assertFalse($result); - } - - public function testValueAbcIsNotValid(): void - { - $result = $this->isValid('abc'); - - $this->assertFalse($result); - } - - public function testValueAbc123IsNotValid(): void - { - $result = $this->isValid('abc123'); - - $this->assertFalse($result); - } - - public function testValueTrueIsNotValid(): void - { - $result = $this->isValid('true'); - - $this->assertFalse($result); - } - - public function testValueFalseIsNotValid(): void - { - $result = $this->isValid('false'); - - $this->assertFalse($result); - } - - public function testValueUndefinedIsNotValid(): void - { - $result = $this->isValid('undefined'); - - $this->assertFalse($result); - } - - public function testValueInfinityIsNotValid(): void - { - $result = $this->isValid('Infinity'); - - $this->assertFalse($result); - } - - public function testValueNullIsNotValid(): void - { - $result = $this->isValid('null'); - - $this->assertFalse($result); - } - - public function testEmptyStringIsNotValid(): void - { - $result = $this->isValid(''); - - $this->assertFalse($result); - } -} diff --git a/packages/cnpj-val/tests/Pest.php b/packages/cnpj-val/tests/Pest.php new file mode 100644 index 0000000..1f38341 --- /dev/null +++ b/packages/cnpj-val/tests/Pest.php @@ -0,0 +1,13 @@ +in(__DIR__ . DIRECTORY_SEPARATOR . 'Specs'); diff --git a/packages/cnpj-val/tests/Specs/CnpjValidator.spec.php b/packages/cnpj-val/tests/Specs/CnpjValidator.spec.php new file mode 100644 index 0000000..11ee8c8 --- /dev/null +++ b/packages/cnpj-val/tests/Specs/CnpjValidator.spec.php @@ -0,0 +1,297 @@ +getOptions()->getAll())->toBe($defaultOptions->getAll()); + }); + }); + + describe('when called with arguments', function () { + it('uses the provided options instance', function () { + $options = new CnpjValidatorOptions(); + + $Validator = new CnpjValidator($options); + + expect($Validator->getOptions())->toBe($options); + }); + + it('overrides the default options with the provided ones (named arguments)', function () { + $options = [ + 'type' => CnpjValidationType::Numeric, + 'caseSensitive' => false, + ]; + + $Validator = new CnpjValidator(...$options); + + expect($Validator->getOptions()->getAll())->toMatchArray($options); + }); + + it('overrides the default options with the provided ones (`CnpjValidatorOptions` instance)', function () { + $options = new CnpjValidatorOptions( + type: CnpjValidationType::Numeric, + caseSensitive: true, + ); + + $Validator = new CnpjValidator($options); + + expect($Validator->getOptions()->getAll())->toBe($options->getAll()); + }); + }); + }); + + describe('`isValid` method', function () { + $isValidWithNamedOptionsInConstructor = function ($cnpj, $type = null, $caseSensitive = null): bool { + $validator = new CnpjValidator(type: $type, caseSensitive: $caseSensitive); + + return $validator->isValid($cnpj); + }; + + $isValidWithCnpjValidatorOptionsInstanceInConstructor = function ($cnpj, $type = null, $caseSensitive = null): bool { + $options = new CnpjValidatorOptions(type: $type, caseSensitive: $caseSensitive); + $validator = new CnpjValidator($options); + + return $validator->isValid($cnpj); + }; + + $isValidWithNamedOptionsInMethod = function ($cnpj, $type = null, $caseSensitive = null): bool { + $validator = new CnpjValidator(); + + return $validator->isValid($cnpj, type: $type, caseSensitive: $caseSensitive); + }; + + $isValidWithCnpjValidatorOptionsInstanceInMethod = function ($cnpj, $type = null, $caseSensitive = null): bool { + $validator = new CnpjValidator(); + $options = new CnpjValidatorOptions(type: $type, caseSensitive: $caseSensitive); + + return $validator->isValid($cnpj, $options); + }; + + $isValidContexts = [ + [ + 'when options are passed to constructor as named arguments', + $isValidWithNamedOptionsInConstructor, + ], + [ + 'when options are passed to constructor as `CnpjValidatorOptions` instance', + $isValidWithCnpjValidatorOptionsInstanceInConstructor, + ], + [ + 'when options are passed to method as named arguments', + $isValidWithNamedOptionsInMethod, + ], + [ + 'when options are passed to method as `CnpjValidatorOptions` instance', + $isValidWithCnpjValidatorOptionsInstanceInMethod, + ], + ]; + + /** + * @return array> + */ + function create_inputs_set(string $cnpj) + { + $unformattedString = $cnpj; + $formattedString = preg_replace( + '/([0-9A-Z]{2})([0-9A-Z]{3})([0-9A-Z]{3})([0-9A-Z]{4})(\d+)/i', + '$1.$2.$3/$4-$5', + $cnpj, + ) ?? ''; + $unformattedArray = str_split($unformattedString); + $formattedArray = str_split($formattedString); + $groupedArray = preg_split('/[.\/\-]/', $formattedString) ?: []; + + return [ + 'string' => $unformattedString, + 'formatted string' => $formattedString, + 'array' => $unformattedArray, + 'formatted array' => $formattedArray, + 'grouped array' => $groupedArray, + ]; + } + + foreach ($isValidContexts as $isValidContext) { + [$description, $isValid] = $isValidContext; + + describe($description, function () use ($isValid) { + describe('when no options are passed', function () use ($isValid) { + $inputsSet = create_inputs_set('1QB5UKALPYFP59'); + + foreach ($inputsSet as $type => $input) { + it("returns `true` for a valid CNPJ {$type} with numbers and uppercase letters", function () use ($isValid, $input) { + $result = $isValid($input); + + expect($result)->toBeTrue(); + }); + } + + $inputsSet = create_inputs_set('96206256120884'); + + foreach ($inputsSet as $type => $input) { + it("returns `true` for a valid CNPJ {$type} with only numbers", function () use ($isValid, $input) { + $result = $isValid($input); + + expect($result)->toBeTrue(); + }); + } + + $inputsSet = create_inputs_set('AB123CDE00015'); + + foreach ($inputsSet as $type => $input) { + it("returns `false` for a CNPJ {$type} with less than 14 digits", function () use ($isValid, $input) { + $result = $isValid($input); + + expect($result)->toBeFalse(); + }); + } + + $inputsSet = create_inputs_set('AB123CDE0001555'); + + foreach ($inputsSet as $type => $input) { + it("returns `false` for a CNPJ {$type} with more than 14 digits", function () use ($isValid, $input) { + $result = $isValid($input); + + expect($result)->toBeFalse(); + }); + } + + it('returns `false` for a CNPJ with base ID "00000000"', function () use ($isValid) { + for ($i = 0; $i < 100; $i++) { + $input = '00000000' . 'A001' . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + + $result = $isValid($input); + + expect($result)->toBeFalse(); + } + }); + + it('returns `false` for a CNPJ with branch ID "0000"', function () use ($isValid) { + for ($i = 0; $i < 100; $i++) { + $input = 'AB123CDE' . '0000' . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + + $result = $isValid($input); + + expect($result)->toBeFalse(); + } + }); + + it('returns `false` for a CNPJ with all digits the same', function (string $prefix) use ($isValid) { + for ($i = 0; $i < 100; $i++) { + $input = $prefix . str_pad((string) $i, 2, '0', STR_PAD_LEFT); + + $result = $isValid($input); + + expect($result)->toBeFalse(); + } + })->with([ + '111111111111', + '222222222222', + '333333333333', + '444444444444', + '555555555555', + '666666666666', + '777777777777', + '888888888888', + '999999999999', + ]); + }); + + describe('when `caseSensitive` option is `false`', function () use ($isValid) { + $inputsSet = create_inputs_set('1QB5UKALPYFP59'); + + foreach ($inputsSet as $type => $input) { + it("returns `true` for a valid CNPJ {$type} with numbers and lowercase letters", function () use ($isValid, $input) { + $result = $isValid($input, caseSensitive: false); + + expect($result)->toBeTrue(); + }); + } + }); + + describe('when `type` option is `"numeric"`', function () use ($isValid) { + $numericInputsSet = create_inputs_set('96206256120884'); + + foreach ($numericInputsSet as $type => $input) { + it("returns `true` for a valid CNPJ {$type} with only numbers", function () use ($isValid, $input) { + $result = $isValid($input, type: CnpjValidationType::Numeric); + + expect($result)->toBeTrue(); + }); + } + + $alphabeticInputsSet = create_inputs_set('1QB5UKALPYFP59'); + + foreach ($alphabeticInputsSet as $type => $input) { + it("returns `false` for a valid CNPJ {$type} with numbers and uppercase letters", function () use ($isValid, $input) { + $result = $isValid($input, type: CnpjValidationType::Numeric); + + expect($result)->toBeFalse(); + }); + } + }); + }); + } + + describe('when called with invalid arguments', function () { + it('throws a `CnpjValidatorInputTypeError` with `null`', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid(null); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got null.'); + }); + + it('throws a `CnpjValidatorInputTypeError` with integer number', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid(42); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got integer number.'); + }); + + it('throws a `CnpjValidatorInputTypeError` with float number', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid(3.14); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got float number.'); + }); + + it('throws a `CnpjValidatorInputTypeError` with boolean', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid(true); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got boolean.'); + }); + + it('throws a `CnpjValidatorInputTypeError` with object', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid(new stdClass()); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got object.'); + }); + + it('throws a `CnpjValidatorInputTypeError` with array of numbers', function () { + $validator = new CnpjValidator(); + + expect(function () use ($validator) { + $validator->isValid([1, 2, 3]); + })->toThrow(CnpjValidatorInputTypeError::class, 'CNPJ input must be of type string or string[]. Got number[].'); + }); + }); + }); +}); diff --git a/packages/cnpj-val/tests/Specs/CnpjValidatorOptions.spec.php b/packages/cnpj-val/tests/Specs/CnpjValidatorOptions.spec.php new file mode 100644 index 0000000..0813854 --- /dev/null +++ b/packages/cnpj-val/tests/Specs/CnpjValidatorOptions.spec.php @@ -0,0 +1,265 @@ + CnpjValidatorOptions::DEFAULT_TYPE, + 'caseSensitive' => CnpjValidatorOptions::DEFAULT_CASE_SENSITIVE, + ]; + + describe('constructor', function () use ($defaultParameters) { + describe('when called with no parameters', function () use ($defaultParameters) { + it('sets all options to default values', function () use ($defaultParameters) { + $options = new CnpjValidatorOptions(); + + expect($options->getAll())->toBe($defaultParameters); + }); + }); + + describe('when called with all parameters with null values', function () use ($defaultParameters) { + it('sets all options to default values', function () use ($defaultParameters) { + $options = new CnpjValidatorOptions( + type: null, + caseSensitive: null, + ); + + expect($options->getAll())->toBe($defaultParameters); + }); + }); + + describe('when called with all parameters', function () { + it('sets all options to the provided values', function () { + $parameters = [ + 'type' => CnpjValidationType::Numeric, + 'caseSensitive' => false, + ]; + + $options = new CnpjValidatorOptions(...$parameters); + + expect($options->getAll())->toBe($parameters); + }); + }); + + describe('when called with some parameters', function () use ($defaultParameters) { + it('sets only the provided non-nullish values', function () use ($defaultParameters) { + $options = new CnpjValidatorOptions( + type: CnpjValidationType::Numeric, + ); + + expect($options->getAll())->toBe([ + ...$defaultParameters, + 'type' => CnpjValidationType::Numeric, + ]); + }); + }); + + describe('when called with overrides parameters', function () { + it('uses last param option with 2 params', function () { + $options = new CnpjValidatorOptions( + overrides: [ + ['caseSensitive' => false], + ['caseSensitive' => true], + ], + ); + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('uses last param option with 1 array and 1 object instance', function () { + $options = new CnpjValidatorOptions( + overrides: [ + ['caseSensitive' => false], + new CnpjValidatorOptions(caseSensitive: true), + ], + ); + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('uses last param option with 5 params', function () { + $options = new CnpjValidatorOptions( + overrides: [ + ['caseSensitive' => false], + new CnpjValidatorOptions(caseSensitive: true), + ['caseSensitive' => false], + new CnpjValidatorOptions(caseSensitive: true), + ['caseSensitive' => false], + ], + ); + + expect($options->caseSensitive)->toBeFalse(); + }); + }); + }); + + describe('`caseSensitive` property', function () use ($defaultParameters) { + describe('when setting to a boolean value', function () { + it('sets `caseSensitive` to `true`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = true; + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('sets `caseSensitive` to `false`', function () { + $options = new CnpjValidatorOptions(caseSensitive: true); + + $options->caseSensitive = false; + + expect($options->caseSensitive)->toBeFalse(); + }); + }); + + describe('when setting to a nullish value', function () use ($defaultParameters) { + it('sets default value for `null`', function () use ($defaultParameters) { + $options = new CnpjValidatorOptions(caseSensitive: !CnpjValidatorOptions::DEFAULT_CASE_SENSITIVE); + + $options->caseSensitive = null; + + expect($options->caseSensitive)->toBe($defaultParameters['caseSensitive']); + }); + }); + + describe('when setting to a non-boolean value', function () { + it('coerces object value to `true`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = (object) ['not' => 'a boolean']; + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('coerces truthy string value to `true`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = 'not a boolean'; + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('coerces truthy number value to `true`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = 123; + + expect($options->caseSensitive)->toBeTrue(); + }); + + it('coerces empty string value to `false`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = ''; + + expect($options->caseSensitive)->toBeFalse(); + }); + + it('coerces zero number value to `false`', function () { + $options = new CnpjValidatorOptions(caseSensitive: false); + + $options->caseSensitive = 0; + + expect($options->caseSensitive)->toBeFalse(); + }); + }); + }); + + describe('`type` property', function () use ($defaultParameters) { + describe('when setting to a `CnpjValidationType` enum', function () { + it("sets `type` to the `CnpjValidationType::Alphanumeric` value", function () { + $options = new CnpjValidatorOptions(type: CnpjValidationType::Numeric); + + $options->type = CnpjValidationType::Alphanumeric; + + expect($options->type)->toBe(CnpjValidationType::Alphanumeric); + }); + + it("sets `type` to the 'CnpjType::Numeric' value", function () { + $options = new CnpjValidatorOptions(type: CnpjValidationType::Alphanumeric); + + $options->type = CnpjValidationType::Numeric; + + expect($options->type)->toBe(CnpjValidationType::Numeric); + }); + }); + + describe('when setting to a string value', function () { + it('sets `type` to the "alphanumeric" value', function () { + $options = new CnpjValidatorOptions(type: CnpjValidationType::Numeric); + + $options->type = 'alphanumeric'; + + expect($options->type)->toBe(CnpjValidationType::Alphanumeric); + }); + + it('sets `type` to the "numeric" value', function () { + $options = new CnpjValidatorOptions(type: 'alphanumeric'); + + $options->type = 'numeric'; + + expect($options->type)->toBe(CnpjValidationType::Numeric); + }); + }); + + describe('when setting to a nullish value', function () use ($defaultParameters) { + it('sets default value for `null`', function () use ($defaultParameters) { + $options = new CnpjValidatorOptions(type: 'numeric'); + + $options->type = null; + + expect($options->type)->toBe($defaultParameters['type']); + }); + }); + + describe('when setting to an invalid string value', function () { + it('throws `CnpjValidatorOptionTypeInvalidException` with an invalid string value', function () { + $options = new CnpjValidatorOptions(); + + expect(function () use ($options) { + $options->type = 'invalid'; + })->toThrow(CnpjValidatorOptionTypeInvalidException::class, 'CNPJ validator option "type" accepts only the following values: "alphanumeric", "numeric". Got "invalid".'); + }); + }); + + describe('when setting to an invalid value type', function () { + it('throws `CnpjValidatorOptionsTypeError` with an object', function () { + $options = new CnpjValidatorOptions(); + + expect(function () use ($options) { + $options->type = (object) ['not' => 'a string']; + })->toThrow(CnpjValidatorOptionsTypeError::class, 'CNPJ validator option "type" must be of type CnpjValidationType or string. Got object.'); + }); + + it('throws `CnpjValidatorOptionsTypeError` with a number', function () { + $options = new CnpjValidatorOptions(); + + expect(function () use ($options) { + $options->type = 123; + })->toThrow(CnpjValidatorOptionsTypeError::class, 'CNPJ validator option "type" must be of type CnpjValidationType or string. Got integer number.'); + }); + + it('throws `CnpjValidatorOptionsTypeError` with a boolean', function () { + $options = new CnpjValidatorOptions(); + + expect(function () use ($options) { + $options->type = true; + })->toThrow(CnpjValidatorOptionsTypeError::class, 'CNPJ validator option "type" must be of type CnpjValidationType or string. Got boolean.'); + }); + }); + }); + + describe('`getAll` method', function () { + it('returns the all properties with expected types', function () { + $all = (new CnpjValidatorOptions())->getAll(); + + expect($all['caseSensitive'])->toBeBool(); + expect($all['type'])->toBeInstanceOf(CnpjValidationType::class); + }); + }); +}); diff --git a/packages/cnpj-val/tests/Specs/Exceptions.spec.php b/packages/cnpj-val/tests/Specs/Exceptions.spec.php new file mode 100644 index 0000000..84c3080 --- /dev/null +++ b/packages/cnpj-val/tests/Specs/Exceptions.spec.php @@ -0,0 +1,245 @@ +toBeInstanceOf(TypeError::class); + }); + + it('is an instance of `CnpjValidatorTypeError`', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error)->toBeInstanceOf(CnpjValidatorTypeError::class); + }); + + it('sets the `actualInput` property', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error->actualInput)->toBe(123); + }); + + it('sets the `actualType` property', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error->actualType)->toBe('number'); + }); + + it('sets the `expectedType` property', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error->expectedType)->toBe('string'); + }); + + it('has the correct message', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error->getMessage())->toBe('some error'); + }); + + it('has the correct name', function () { + $error = new TestTypeError(123, 'number', 'string', 'some error'); + + expect($error->getName())->toBe('TestTypeError'); + }); + }); +}); + +describe('CnpjValidatorInputTypeError', function () { + describe('when instantiated', function () { + it('is an instance of `TypeError`', function () { + $error = new CnpjValidatorInputTypeError(123, 'string'); + + expect($error)->toBeInstanceOf(TypeError::class); + }); + + it('is an instance of `CnpjValidatorTypeError`', function () { + $error = new CnpjValidatorInputTypeError(123, 'string'); + + expect($error)->toBeInstanceOf(CnpjValidatorTypeError::class); + }); + + it('sets the `actualInput` property', function () { + $error = new CnpjValidatorInputTypeError(123, 'string'); + + expect($error->actualInput)->toBe(123); + }); + + it('sets the `actualType` property', function () { + $error = new CnpjValidatorInputTypeError(123, 'string'); + + expect($error->actualType)->toBe('integer number'); + }); + + it('sets the `expectedType` property', function () { + $error = new CnpjValidatorInputTypeError(123, 'string or string[]'); + + expect($error->expectedType)->toBe('string or string[]'); + }); + + it('has the correct message', function () { + $actualInput = 123; + $actualType = 'integer number'; + $expectedType = 'string or string[]'; + $message = "CNPJ input must be of type {$expectedType}. Got {$actualType}."; + + $error = new CnpjValidatorInputTypeError($actualInput, $expectedType); + + expect($error->getMessage())->toBe($message); + }); + + it('has the correct name', function () { + $error = new CnpjValidatorInputTypeError(123, 'string or string[]'); + + expect($error->getName())->toBe('CnpjValidatorInputTypeError'); + }); + }); +}); + +describe('CnpjValidatorOptionsTypeError', function () { + describe('when instantiated', function () { + it('is an instance of `TypeError`', function () { + $error = new CnpjValidatorOptionsTypeError('type', 123, 'boolean'); + + expect($error)->toBeInstanceOf(TypeError::class); + }); + + it('is an instance of `CnpjValidatorTypeError`', function () { + $error = new CnpjValidatorOptionsTypeError('type', 123, 'boolean'); + + expect($error)->toBeInstanceOf(CnpjValidatorTypeError::class); + }); + + it('sets the `optionName` property', function () { + $error = new CnpjValidatorOptionsTypeError('caseSensitive', 123, 'boolean'); + + expect($error->optionName)->toBe('caseSensitive'); + }); + + it('sets the `actualInput` property', function () { + $error = new CnpjValidatorOptionsTypeError('caseSensitive', 123, 'boolean'); + + expect($error->actualInput)->toBe(123); + }); + + it('sets the `actualType` property', function () { + $error = new CnpjValidatorOptionsTypeError('caseSensitive', 123, 'boolean'); + + expect($error->actualType)->toBe('integer number'); + }); + + it('sets the `expectedType` property', function () { + $error = new CnpjValidatorOptionsTypeError('caseSensitive', 123, 'boolean'); + + expect($error->expectedType)->toBe('boolean'); + }); + + it('has the correct message', function () { + $optionName = 'caseSensitive'; + $actualInput = 123; + $actualInputType = 'integer number'; + $expectedType = 'boolean'; + $message = "CNPJ validator option \"{$optionName}\" must be of type {$expectedType}. Got {$actualInputType}."; + + $error = new CnpjValidatorOptionsTypeError($optionName, $actualInput, $expectedType); + + expect($error->getMessage())->toBe($message); + }); + + it('has the correct name', function () { + $error = new CnpjValidatorOptionsTypeError('caseSensitive', 123, 'boolean'); + + expect($error->getName())->toBe('CnpjValidatorOptionsTypeError'); + }); + }); +}); + +describe('CnpjValidatorException', function () { + describe('when instantiated through a subclass', function () { + final class TestException extends CnpjValidatorException + { + } + + it('is an instance of `Exception`', function () { + $exception = new TestException('some error'); + + expect($exception)->toBeInstanceOf(Exception::class); + }); + + it('is an instance of `CnpjValidatorException`', function () { + $exception = new TestException('some error'); + + expect($exception)->toBeInstanceOf(CnpjValidatorException::class); + }); + + it('has the correct message', function () { + $exception = new TestException('some exception'); + + expect($exception->getMessage())->toBe('some exception'); + }); + + it('has the correct name', function () { + $exception = new TestException('some error'); + + expect($exception->getName())->toBe('TestException'); + }); + }); +}); + +describe('CnpjValidatorOptionTypeInvalidException', function () { + describe('when instantiated', function () { + it('is an instance of `Exception`', function () { + $exception = new CnpjValidatorOptionTypeInvalidException('test', ['foo', 'bar', 'baz']); + + expect($exception)->toBeInstanceOf(Exception::class); + }); + + it('is an instance of `CnpjValidatorException`', function () { + $exception = new CnpjValidatorOptionTypeInvalidException('test', ['foo', 'bar', 'baz']); + + expect($exception)->toBeInstanceOf(CnpjValidatorException::class); + }); + + it('sets the `actualInput` property', function () { + $exception = new CnpjValidatorOptionTypeInvalidException('test', ['foo', 'bar', 'baz']); + + expect($exception->actualInput)->toBe('test'); + }); + + it('sets the `expectedValues` property', function () { + $exception = new CnpjValidatorOptionTypeInvalidException('test', ['foo', 'bar', 'baz']); + + expect($exception->expectedValues)->toBe(['foo', 'bar', 'baz']); + }); + + it('has the correct message', function () { + $actualInput = 'test'; + $expectedValues = ['foo', 'bar', 'baz']; + $expectedValuesString = implode('", "', $expectedValues); + $message = "CNPJ validator option \"type\" accepts only the following values: \"{$expectedValuesString}\". Got \"{$actualInput}\"."; + + $exception = new CnpjValidatorOptionTypeInvalidException($actualInput, $expectedValues); + + expect($exception->getMessage())->toBe($message); + }); + + it('has the correct name', function () { + $exception = new CnpjValidatorOptionTypeInvalidException('test', ['foo', 'bar', 'baz']); + + expect($exception->getName())->toBe('CnpjValidatorOptionTypeInvalidException'); + }); + }); +}); diff --git a/packages/cnpj-val/tests/Specs/cnpj-val.spec.php b/packages/cnpj-val/tests/Specs/cnpj-val.spec.php new file mode 100644 index 0000000..2373a9d --- /dev/null +++ b/packages/cnpj-val/tests/Specs/cnpj-val.spec.php @@ -0,0 +1,30 @@ +toBe($validator->isValid($input)); + }); + + it('accepts options and forwards validation behavior', function () { + $input = '01ABC234000X56'; + $options = ['type' => CnpjValidationType::Numeric]; + + $result = cnpj_val($input, ...$options); + + expect($result)->toBeFalse(); + }); + }); +});