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 @@
[](https://github.com/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
-|  |  |  |  |
-|--- | --- | --- | --- |
+|  |  |  |  |
+| --- | --- | --- | --- |
| 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 @@
+
+
+> 🚀 **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();
+ });
+ });
+});