Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- RENAVAM instance for vehicle registration management.
- Unit tests for RENAVAM validation, formatting, and generation.
- Functions for RENAVAM validation and generation.

## [2.0.0] - 2025-03-26

### Added
Expand Down
8 changes: 8 additions & 0 deletions brazilcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/potatowski/brazilcode/v2/src/cnpj"
"github.com/potatowski/brazilcode/v2/src/cpf"
iface "github.com/potatowski/brazilcode/v2/src/interface"
"github.com/potatowski/brazilcode/v2/src/renavam"
"github.com/potatowski/brazilcode/v2/src/voterRegistration"
)

Expand All @@ -29,6 +30,12 @@ var CNH = cnh.CNH{}
// registration information.
var VoterRegistration = voterRegistration.VoterRegistration{}

// RENAVAM is an instance of the renavam.RENAVAM struct, which represents
// the Brazilian National Registry of Motor Vehicles (Registro Nacional de
// Veículos Automotores). It is used to manage and validate vehicle
// registration information in Brazil.
var RENAVAM = renavam.RENAVAM{}

// Documents is a map that associates document types with their corresponding
// iface.Document implementations. The keys represent the document type names
// (e.g., "CPF", "CNPJ", "CNH", "VoterRegistration"), and the values are the
Expand All @@ -38,6 +45,7 @@ var Documents = map[string]iface.Document{
"CNPJ": CNPJ,
"CNH": CNH,
"VoterRegistration": VoterRegistration,
"RENAVAM": RENAVAM,
}

// IsValid checks if the provided document is valid based on its type.
Expand Down
94 changes: 94 additions & 0 deletions src/renavam/renavam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package renavam

import (
"fmt"

"github.com/potatowski/brazilcode/v2/src/utils"
)

var (
ErrRenavamInvalidLength = fmt.Errorf("invalid RENAVAM length")
ErrRenavamInvalid = fmt.Errorf("invalid RENAVAM")
)

var pesos = []int{3, 2, 9, 8, 7, 6, 5, 4, 3, 2}

type RENAVAM struct{}

// IsValid validates a RENAVAM (Brazilian vehicle registration number) document.
// It checks if the provided document has the correct length and verifies its checksum.
//
// Parameters:
//
// doc (string): The RENAVAM document to be validated.
//
// Returns:
//
// error: Returns ErrRenavamInvalidLength if the document does not have 11 characters.
// Returns ErrRenavamInvalid if the checksum validation fails.
// Returns nil if the document is valid.
func (iDoc RENAVAM) IsValid(doc string) error {
doc = utils.RemoveChar(doc)
if len(doc) != 11 {
return ErrRenavamInvalidLength
}

var sum int
for i := 0; i < len(doc)-1; i++ {
sum += int(doc[i]-'0') * pesos[i]
}

digit := utils.GetDigit(sum)
if int(doc[len(doc)-1]-'0') != digit {
return ErrRenavamInvalid
}

return nil
}

// Generate creates a new RENAVAM (Brazilian vehicle registration number)
// by generating a random base document number, calculating its checksum
// using predefined weights, and appending the resulting digit to the base.
// Returns:
//
// doc (string): The RENAVAM document.
func (iDoc RENAVAM) Generate() (string, error) {
doc := utils.GenerateRandomDoc(10, 9)
var sum int
for i := 0; i < len(doc); i++ {
sum += int(doc[i]-'0') * pesos[i]
}

digit := utils.GetDigit(sum)
doc = fmt.Sprintf("%s%d", doc, digit)
if err := iDoc.IsValid(doc); err != nil {
return "", err
}

return doc, nil
}

// Format formats a RENAVAM document string by removing any non-numeric characters
// and applying a specific pattern. The formatted output separates the first 8 digits
// from the last 3 digits with a hyphen.
//
// Parameters:
//
// doc - The RENAVAM document string to be formatted.
//
// Returns:
//
// A formatted RENAVAM string in the pattern "XXXXXXXXXXX" if the input is valid,
// or an error if the input length is not exactly 11 characters.
//
// Errors:
//
// ErrRenavamInvalidLength - Returned if the input string does not have exactly 11 characters.
func (iDoc RENAVAM) Format(doc string) (string, error) {
err := iDoc.IsValid(doc)
if err != nil {
return "", err
}

return doc, nil
}
88 changes: 88 additions & 0 deletions src/renavam/renavam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package renavam

import (
"testing"

iface "github.com/potatowski/brazilcode/v2/src/interface"
)

var doc iface.Document = RENAVAM{}

func TestIsValid(t *testing.T) {
testCases := []struct {
name string
doc string
expected error
}{
{
name: "Valid RENAVAM",
doc: "62959061142",
expected: nil,
},
{
name: "Invalid RENAVAM - wrong length",
doc: "6295906114",
expected: ErrRenavamInvalidLength,
},
{
name: "Invalid RENAVAM",
doc: "62959061141",
expected: ErrRenavamInvalid,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := doc.IsValid(tc.doc)
if err != tc.expected {
t.Errorf("Expected error to be '%v' but got '%v'", tc.expected, err)
}
})
}
}

func TestFormat(t *testing.T) {
testCases := []struct {
name string
doc string
expected string
expectedError error
}{
{
name: "Valid RENAVAM",
doc: "62959061142",
expected: "62959061142",
expectedError: nil,
},
{
name: "Invalid RENAVAM",
doc: "62959061141",
expected: "",
expectedError: ErrRenavamInvalid,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result, err := doc.Format(tc.doc)
if err != nil && err != tc.expectedError {
t.Errorf("Expected error to be '%v' but got '%v'", tc.expectedError, err)
}

if result != tc.expected {
t.Errorf("Expected result to be '%v' but got '%v'", tc.expected, result)
}
})
}
}

func TestGenerate(t *testing.T) {
renavam, err := doc.Generate()
if err != nil {
t.Errorf("[TEST-RENAVAM-generate] unexpected error: %v\n RENAVAM generated: %s", err, renavam)
}

if len(renavam) != 11 {
t.Errorf("[TEST-RENAVAM-generate] unexpected result: generated RENAVAM has invalid length, got %d", len(renavam))
}
}