feat(payments): filter candidates by DNI distance
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Domains\Purchase\Models\Purchase;
|
|||||||
use App\Domains\Purchase\Models\TelepagosPayment;
|
use App\Domains\Purchase\Models\TelepagosPayment;
|
||||||
use App\Domains\Purchase\Models\TelepagosQr;
|
use App\Domains\Purchase\Models\TelepagosQr;
|
||||||
use App\Domains\Purchase\Services\CheckoutService;
|
use App\Domains\Purchase\Services\CheckoutService;
|
||||||
|
use App\Domains\Purchase\Services\DniDistanceService;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
@@ -17,6 +18,7 @@ class TelepagosWebhookService
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly CheckoutService $checkoutService,
|
private readonly CheckoutService $checkoutService,
|
||||||
|
private readonly DniDistanceService $dniDistance,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,10 +86,12 @@ class TelepagosWebhookService
|
|||||||
->where('payment_method', 'transfer');
|
->where('payment_method', 'transfer');
|
||||||
|
|
||||||
$purchases = (clone $eligiblePurchases)
|
$purchases = (clone $eligiblePurchases)
|
||||||
->where('transfer_payer_dni', $dni)
|
|
||||||
->where('total', $amount)
|
->where('total', $amount)
|
||||||
->latest()
|
->latest()
|
||||||
->get();
|
->get()
|
||||||
|
->filter(fn (Purchase $purchase): bool => $purchase->transfer_payer_dni !== null
|
||||||
|
&& $this->dniDistance->distance($dni, $purchase->transfer_payer_dni) === 0)
|
||||||
|
->values();
|
||||||
|
|
||||||
$compra = $purchases->count() === 1 ? $purchases->first() : null;
|
$compra = $purchases->count() === 1 ? $purchases->first() : null;
|
||||||
|
|
||||||
@@ -115,6 +119,7 @@ class TelepagosWebhookService
|
|||||||
->map(fn ($candidate): array => [
|
->map(fn ($candidate): array => [
|
||||||
'purchase_id' => $candidate->compra_id,
|
'purchase_id' => $candidate->compra_id,
|
||||||
'match_reason' => $candidate->match_reason,
|
'match_reason' => $candidate->match_reason,
|
||||||
|
'dni_distance' => $candidate->dni_distance,
|
||||||
'amount_difference' => $candidate->amount_difference,
|
'amount_difference' => $candidate->amount_difference,
|
||||||
'confidence' => $candidate->confidence,
|
'confidence' => $candidate->confidence,
|
||||||
])
|
])
|
||||||
@@ -255,25 +260,24 @@ class TelepagosWebhookService
|
|||||||
$maximumAmount = $this->normalizeAmount($numericAmount + $tolerance);
|
$maximumAmount = $this->normalizeAmount($numericAmount + $tolerance);
|
||||||
|
|
||||||
return (clone $eligiblePurchases)
|
return (clone $eligiblePurchases)
|
||||||
->where(function ($query) use ($dni, $amount, $minimumAmount, $maximumAmount): void {
|
->whereBetween('total', [$minimumAmount, $maximumAmount])
|
||||||
$query
|
|
||||||
->where(function ($query) use ($dni, $minimumAmount, $maximumAmount): void {
|
|
||||||
$query
|
|
||||||
->where('transfer_payer_dni', $dni)
|
|
||||||
->whereBetween('total', [$minimumAmount, $maximumAmount]);
|
|
||||||
})
|
|
||||||
->orWhere(function ($query) use ($dni, $amount): void {
|
|
||||||
$query
|
|
||||||
->where('total', $amount)
|
|
||||||
->where(function ($query) use ($dni): void {
|
|
||||||
$query
|
|
||||||
->whereNull('transfer_payer_dni')
|
|
||||||
->orWhere('transfer_payer_dni', '!=', $dni);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
->latest()
|
->latest()
|
||||||
->get();
|
->get()
|
||||||
|
->filter(function (Purchase $purchase) use ($dni, $amount): bool {
|
||||||
|
if ($purchase->transfer_payer_dni === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$purchaseAmount = $this->normalizeAmount($purchase->total);
|
||||||
|
$distance = $this->dniDistance->distance(
|
||||||
|
$dni,
|
||||||
|
(string) $purchase->transfer_payer_dni,
|
||||||
|
);
|
||||||
|
|
||||||
|
return $distance === 0
|
||||||
|
|| ($purchaseAmount === $amount && $distance <= 2);
|
||||||
|
})
|
||||||
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,12 +297,19 @@ class TelepagosWebhookService
|
|||||||
$candidatePurchases
|
$candidatePurchases
|
||||||
->map(function (Purchase $purchase) use ($dni, $amount): array {
|
->map(function (Purchase $purchase) use ($dni, $amount): array {
|
||||||
$purchaseAmount = $this->normalizeAmount($purchase->total);
|
$purchaseAmount = $this->normalizeAmount($purchase->total);
|
||||||
$dniMatches = $purchase->transfer_payer_dni === $dni;
|
$dniDistance = $this->dniDistance->distance(
|
||||||
|
$dni,
|
||||||
|
(string) $purchase->transfer_payer_dni,
|
||||||
|
);
|
||||||
|
$dniMatches = $dniDistance === 0;
|
||||||
$amountMatches = $purchaseAmount === $amount;
|
$amountMatches = $purchaseAmount === $amount;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'compra_id' => $purchase->id,
|
'compra_id' => $purchase->id,
|
||||||
'dni_matches' => $dniMatches,
|
'dni_matches' => $dniMatches,
|
||||||
|
'dni_distance' => $dniDistance,
|
||||||
|
'payment_dni' => $dni,
|
||||||
|
'purchase_dni' => $purchase->transfer_payer_dni,
|
||||||
'amount_matches' => $amountMatches,
|
'amount_matches' => $amountMatches,
|
||||||
'payment_amount' => $amount,
|
'payment_amount' => $amount,
|
||||||
'purchase_amount' => $purchaseAmount,
|
'purchase_amount' => $purchaseAmount,
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||||||
'telepagos_payment_id',
|
'telepagos_payment_id',
|
||||||
'compra_id',
|
'compra_id',
|
||||||
'dni_matches',
|
'dni_matches',
|
||||||
|
'dni_distance',
|
||||||
|
'payment_dni',
|
||||||
|
'purchase_dni',
|
||||||
'amount_matches',
|
'amount_matches',
|
||||||
'payment_amount',
|
'payment_amount',
|
||||||
'purchase_amount',
|
'purchase_amount',
|
||||||
@@ -27,6 +30,7 @@ class TelepagosPaymentCandidate extends Model
|
|||||||
'telepagos_payment_id' => 'integer',
|
'telepagos_payment_id' => 'integer',
|
||||||
'compra_id' => 'integer',
|
'compra_id' => 'integer',
|
||||||
'dni_matches' => 'boolean',
|
'dni_matches' => 'boolean',
|
||||||
|
'dni_distance' => 'integer',
|
||||||
'amount_matches' => 'boolean',
|
'amount_matches' => 'boolean',
|
||||||
'payment_amount' => 'decimal:2',
|
'payment_amount' => 'decimal:2',
|
||||||
'purchase_amount' => 'decimal:2',
|
'purchase_amount' => 'decimal:2',
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ class PurchaseResource extends JsonResource
|
|||||||
'candidate_count' => $candidates->count(),
|
'candidate_count' => $candidates->count(),
|
||||||
'primary' => $primary === null ? null : [
|
'primary' => $primary === null ? null : [
|
||||||
'reason' => $primary->match_reason,
|
'reason' => $primary->match_reason,
|
||||||
|
'dni_distance' => $primary->dni_distance,
|
||||||
'payment_amount' => $this->formatMoney($primary->payment_amount),
|
'payment_amount' => $this->formatMoney($primary->payment_amount),
|
||||||
'purchase_amount' => $this->formatMoney($primary->purchase_amount),
|
'purchase_amount' => $this->formatMoney($primary->purchase_amount),
|
||||||
'amount_difference' => $this->formatMoney($primary->amount_difference),
|
'amount_difference' => $this->formatMoney($primary->amount_difference),
|
||||||
|
|||||||
82
app/Domains/Purchase/Services/DniDistanceService.php
Normal file
82
app/Domains/Purchase/Services/DniDistanceService.php
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\Purchase\Services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Measures likely DNI typing errors using the optimal-string-alignment
|
||||||
|
* variant of the Damerau-Levenshtein distance.
|
||||||
|
*
|
||||||
|
* The returned value is the minimum number of single-character edits needed
|
||||||
|
* to transform one DNI into the other. Supported edits are insertion,
|
||||||
|
* deletion, substitution and transposition of two adjacent digits.
|
||||||
|
*/
|
||||||
|
class DniDistanceService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Calculate the edit distance between two normalized DNI strings.
|
||||||
|
*
|
||||||
|
* Each matrix cell [row][column] stores the minimum edits required to
|
||||||
|
* transform the first $row digits of $left into the first $column digits
|
||||||
|
* of $right. The bottom-right cell therefore contains the final distance.
|
||||||
|
*/
|
||||||
|
public function distance(string $left, string $right): int
|
||||||
|
{
|
||||||
|
$left = $this->normalize($left);
|
||||||
|
$right = $this->normalize($right);
|
||||||
|
$leftLength = strlen($left);
|
||||||
|
$rightLength = strlen($right);
|
||||||
|
$matrix = [];
|
||||||
|
|
||||||
|
// Transforming a prefix into an empty string requires deleting every digit.
|
||||||
|
for ($row = 0; $row <= $leftLength; $row++) {
|
||||||
|
$matrix[$row] = [$row];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transforming an empty string into a prefix requires inserting every digit.
|
||||||
|
for ($column = 0; $column <= $rightLength; $column++) {
|
||||||
|
$matrix[0][$column] = $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($row = 1; $row <= $leftLength; $row++) {
|
||||||
|
for ($column = 1; $column <= $rightLength; $column++) {
|
||||||
|
$substitutionCost = $left[$row - 1] === $right[$column - 1] ? 0 : 1;
|
||||||
|
$deletionDistance = $matrix[$row - 1][$column] + 1;
|
||||||
|
$insertionDistance = $matrix[$row][$column - 1] + 1;
|
||||||
|
$substitutionDistance = $matrix[$row - 1][$column - 1] + $substitutionCost;
|
||||||
|
|
||||||
|
// Keep the cheapest way to align the two prefixes at this position.
|
||||||
|
$matrix[$row][$column] = min(
|
||||||
|
$deletionDistance,
|
||||||
|
$insertionDistance,
|
||||||
|
$substitutionDistance,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Count two adjacent inverted digits as one edit instead of two substitutions.
|
||||||
|
if (
|
||||||
|
$row > 1
|
||||||
|
&& $column > 1
|
||||||
|
&& $left[$row - 1] === $right[$column - 2]
|
||||||
|
&& $left[$row - 2] === $right[$column - 1]
|
||||||
|
) {
|
||||||
|
$matrix[$row][$column] = min(
|
||||||
|
$matrix[$row][$column],
|
||||||
|
$matrix[$row - 2][$column - 2] + 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $matrix[$leftLength][$rightLength];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keep only digits and left-pad seven-digit DNIs so comparisons preserve
|
||||||
|
* the leading zero that is present when the DNI is extracted from a CUIT.
|
||||||
|
*/
|
||||||
|
public function normalize(string $dni): string
|
||||||
|
{
|
||||||
|
$digits = preg_replace('/\D+/', '', $dni) ?? '';
|
||||||
|
|
||||||
|
return str_pad($digits, 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\Purchase\Services\DniDistanceService;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payment_candidates', function (Blueprint $table) {
|
||||||
|
$table->unsignedTinyInteger('dni_distance')->nullable()->after('dni_matches');
|
||||||
|
$table->string('payment_dni', 8)->nullable()->after('dni_distance');
|
||||||
|
$table->string('purchase_dni', 8)->nullable()->after('payment_dni');
|
||||||
|
});
|
||||||
|
|
||||||
|
$dniDistance = new DniDistanceService;
|
||||||
|
|
||||||
|
DB::table('telepagos_payment_candidates as candidate')
|
||||||
|
->join('telepagos_payments as payment', 'payment.id', '=', 'candidate.telepagos_payment_id')
|
||||||
|
->join('compras as purchase', 'purchase.id', '=', 'candidate.compra_id')
|
||||||
|
->select([
|
||||||
|
'candidate.id',
|
||||||
|
'candidate.match_reason',
|
||||||
|
'payment.cuit_buyer',
|
||||||
|
'purchase.transfer_payer_dni',
|
||||||
|
])
|
||||||
|
->orderBy('candidate.id')
|
||||||
|
->each(function (object $candidate) use ($dniDistance): void {
|
||||||
|
if ($candidate->cuit_buyer === null || $candidate->transfer_payer_dni === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payerDni = substr((string) $candidate->cuit_buyer, 2, -1);
|
||||||
|
$distance = $dniDistance->distance($payerDni, (string) $candidate->transfer_payer_dni);
|
||||||
|
|
||||||
|
if ($candidate->match_reason === 'exact_amount_different_dni' && $distance > 2) {
|
||||||
|
DB::table('telepagos_payment_candidates')->where('id', $candidate->id)->delete();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::table('telepagos_payment_candidates')
|
||||||
|
->where('id', $candidate->id)
|
||||||
|
->update([
|
||||||
|
'dni_distance' => $distance,
|
||||||
|
'payment_dni' => $payerDni,
|
||||||
|
'purchase_dni' => $candidate->transfer_payer_dni,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('telepagos_payment_candidates', function (Blueprint $table) {
|
||||||
|
$table->dropColumn(['dni_distance', 'payment_dni', 'purchase_dni']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -351,6 +351,8 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
|
|
||||||
$nearAmountVariant = $this->createVariantForTenant('candidates', 10, '52.00', 'near');
|
$nearAmountVariant = $this->createVariantForTenant('candidates', 10, '52.00', 'near');
|
||||||
$exactAmountVariant = $this->createVariantForTenant('candidates', 10, '50.00', 'exact');
|
$exactAmountVariant = $this->createVariantForTenant('candidates', 10, '50.00', 'exact');
|
||||||
|
$distanceTwoExactAmountVariant = $this->createVariantForTenant('candidates', 10, '50.00', 'distance-two');
|
||||||
|
$farExactAmountVariant = $this->createVariantForTenant('candidates', 10, '50.00', 'far-exact');
|
||||||
$nearAmountDifferentDniVariant = $this->createVariantForTenant('candidates', 10, '51.00', 'near-other-dni');
|
$nearAmountDifferentDniVariant = $this->createVariantForTenant('candidates', 10, '51.00', 'near-other-dni');
|
||||||
$outsideRangeVariant = $this->createVariantForTenant('candidates', 10, '100.00', 'outside');
|
$outsideRangeVariant = $this->createVariantForTenant('candidates', 10, '100.00', 'outside');
|
||||||
|
|
||||||
@@ -366,8 +368,22 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
User::factory()->create()->id,
|
User::factory()->create()->id,
|
||||||
$exactAmountVariant->id,
|
$exactAmountVariant->id,
|
||||||
1,
|
1,
|
||||||
|
'12345687',
|
||||||
|
);
|
||||||
|
$farExactAmountDifferentDniPurchase = $this->createPendingTransferPurchase(
|
||||||
|
$tenant,
|
||||||
|
User::factory()->create()->id,
|
||||||
|
$farExactAmountVariant->id,
|
||||||
|
1,
|
||||||
'87654321',
|
'87654321',
|
||||||
);
|
);
|
||||||
|
$distanceTwoExactAmountPurchase = $this->createPendingTransferPurchase(
|
||||||
|
$tenant,
|
||||||
|
User::factory()->create()->id,
|
||||||
|
$distanceTwoExactAmountVariant->id,
|
||||||
|
1,
|
||||||
|
'12345087',
|
||||||
|
);
|
||||||
$nearAmountDifferentDniPurchase = $this->createPendingTransferPurchase(
|
$nearAmountDifferentDniPurchase = $this->createPendingTransferPurchase(
|
||||||
$tenant,
|
$tenant,
|
||||||
User::factory()->create()->id,
|
User::factory()->create()->id,
|
||||||
@@ -412,11 +428,15 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
$this->assertEqualsCanonicalizing([
|
$this->assertEqualsCanonicalizing([
|
||||||
$nearAmountPurchase->id,
|
$nearAmountPurchase->id,
|
||||||
$exactAmountDifferentDniPurchase->id,
|
$exactAmountDifferentDniPurchase->id,
|
||||||
|
$distanceTwoExactAmountPurchase->id,
|
||||||
], $payment->candidates()->pluck('compra_id')->all());
|
], $payment->candidates()->pluck('compra_id')->all());
|
||||||
$this->assertDatabaseHas('telepagos_payment_candidates', [
|
$this->assertDatabaseHas('telepagos_payment_candidates', [
|
||||||
'telepagos_payment_id' => $payment->id,
|
'telepagos_payment_id' => $payment->id,
|
||||||
'compra_id' => $nearAmountPurchase->id,
|
'compra_id' => $nearAmountPurchase->id,
|
||||||
'dni_matches' => true,
|
'dni_matches' => true,
|
||||||
|
'dni_distance' => 0,
|
||||||
|
'payment_dni' => '12345678',
|
||||||
|
'purchase_dni' => '12345678',
|
||||||
'amount_matches' => false,
|
'amount_matches' => false,
|
||||||
'payment_amount' => 50,
|
'payment_amount' => 50,
|
||||||
'purchase_amount' => 52,
|
'purchase_amount' => 52,
|
||||||
@@ -428,6 +448,9 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
'telepagos_payment_id' => $payment->id,
|
'telepagos_payment_id' => $payment->id,
|
||||||
'compra_id' => $exactAmountDifferentDniPurchase->id,
|
'compra_id' => $exactAmountDifferentDniPurchase->id,
|
||||||
'dni_matches' => false,
|
'dni_matches' => false,
|
||||||
|
'dni_distance' => 1,
|
||||||
|
'payment_dni' => '12345678',
|
||||||
|
'purchase_dni' => '12345687',
|
||||||
'amount_matches' => true,
|
'amount_matches' => true,
|
||||||
'payment_amount' => 50,
|
'payment_amount' => 50,
|
||||||
'purchase_amount' => 50,
|
'purchase_amount' => 50,
|
||||||
@@ -435,6 +458,20 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
'match_reason' => 'exact_amount_different_dni',
|
'match_reason' => 'exact_amount_different_dni',
|
||||||
'confidence' => 'medium',
|
'confidence' => 'medium',
|
||||||
]);
|
]);
|
||||||
|
$this->assertDatabaseHas('telepagos_payment_candidates', [
|
||||||
|
'telepagos_payment_id' => $payment->id,
|
||||||
|
'compra_id' => $distanceTwoExactAmountPurchase->id,
|
||||||
|
'dni_matches' => false,
|
||||||
|
'dni_distance' => 2,
|
||||||
|
'payment_dni' => '12345678',
|
||||||
|
'purchase_dni' => '12345087',
|
||||||
|
'amount_matches' => true,
|
||||||
|
'match_reason' => 'exact_amount_different_dni',
|
||||||
|
]);
|
||||||
|
$this->assertDatabaseMissing('telepagos_payment_candidates', [
|
||||||
|
'telepagos_payment_id' => $payment->id,
|
||||||
|
'compra_id' => $farExactAmountDifferentDniPurchase->id,
|
||||||
|
]);
|
||||||
$this->assertDatabaseMissing('telepagos_payment_candidates', [
|
$this->assertDatabaseMissing('telepagos_payment_candidates', [
|
||||||
'telepagos_payment_id' => $payment->id,
|
'telepagos_payment_id' => $payment->id,
|
||||||
'compra_id' => $nearAmountDifferentDniPurchase->id,
|
'compra_id' => $nearAmountDifferentDniPurchase->id,
|
||||||
@@ -455,6 +492,9 @@ class TelepagosWebhookTest extends TestCase
|
|||||||
$higherPriorityPayment->candidates()->create([
|
$higherPriorityPayment->candidates()->create([
|
||||||
'compra_id' => $nearAmountPurchase->id,
|
'compra_id' => $nearAmountPurchase->id,
|
||||||
'dni_matches' => true,
|
'dni_matches' => true,
|
||||||
|
'dni_distance' => 0,
|
||||||
|
'payment_dni' => '12345678',
|
||||||
|
'purchase_dni' => '12345678',
|
||||||
'amount_matches' => true,
|
'amount_matches' => true,
|
||||||
'payment_amount' => 52,
|
'payment_amount' => 52,
|
||||||
'purchase_amount' => 52,
|
'purchase_amount' => 52,
|
||||||
|
|||||||
31
tests/Unit/Purchase/DniDistanceServiceTest.php
Normal file
31
tests/Unit/Purchase/DniDistanceServiceTest.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Unit\Purchase;
|
||||||
|
|
||||||
|
use App\Domains\Purchase\Services\DniDistanceService;
|
||||||
|
use PHPUnit\Framework\Attributes\DataProvider;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class DniDistanceServiceTest extends TestCase
|
||||||
|
{
|
||||||
|
/** @return iterable<string, array{string, string, int}> */
|
||||||
|
public static function distances(): iterable
|
||||||
|
{
|
||||||
|
yield 'exact' => ['40123456', '40123456', 0];
|
||||||
|
yield 'leading transposition' => ['40123456', '04123456', 1];
|
||||||
|
yield 'trailing transposition' => ['40123456', '40123465', 1];
|
||||||
|
yield 'single substitution' => ['40123456', '40123856', 1];
|
||||||
|
yield 'substitution and transposition' => ['12345678', '12345087', 2];
|
||||||
|
yield 'seven digits normalized with leading zero' => ['04123456', '4123456', 0];
|
||||||
|
yield 'more than two edits' => ['40123456', '87654321', 7];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('distances')]
|
||||||
|
public function test_it_calculates_damerau_levenshtein_distance(
|
||||||
|
string $left,
|
||||||
|
string $right,
|
||||||
|
int $expected,
|
||||||
|
): void {
|
||||||
|
$this->assertSame($expected, (new DniDistanceService)->distance($left, $right));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user