'env', 'dhl.international_countries' => ['AT', 'ES'], ]); }); it('resolves domestic DHL products for Germany', function () { $resolver = new DhlProductResolver; expect($resolver->resolveForShipment('DE', 'V62KP'))->toMatchArray([ 'country_code' => 'DE', 'dhl_country_code' => 'DEU', 'product_code' => 'V62KP', ]); }); it('suggests international parcel for Austria and Spain', function (string $countryCode) { $resolver = new DhlProductResolver; expect($resolver->resolveProductCode($countryCode, null, 'V01PAK'))->toBe('V53WPAK'); })->with([ 'austria' => 'AT', 'spain' => 'ES', ]); it('rejects unsupported destination countries', function () { (new DhlProductResolver)->resolveProductCode('FR', null, 'V01PAK'); })->throws(InvalidArgumentException::class, 'DHL-Versand in das Zielland FR ist aktuell nicht freigegeben.'); it('uses configured international destination countries', function () { $previousConfigSource = config('dhl.config_source'); $previousCountries = config('dhl.international_countries'); config([ 'dhl.config_source' => 'env', 'dhl.international_countries' => ['AT', 'FR'], ]); try { $resolver = new DhlProductResolver; expect($resolver->resolveProductCode('FR', null, 'V01PAK'))->toBe('V53WPAK') ->and($resolver->getProductSuggestionsByCountry())->toMatchArray([ 'DE' => 'V01PAK', 'FR' => 'V53WPAK', ]); } finally { config([ 'dhl.config_source' => $previousConfigSource, 'dhl.international_countries' => $previousCountries, ]); } }); it('uses saved DHL international countries even with env config priority', function () { $createdSettingsTable = ensureDhlSettingsTableForResolverTest(); try { config([ 'dhl.config_source' => 'env', 'dhl.international_countries' => ['AT'], ]); Setting::whereSlug('dhl_international_countries')->delete(); Setting::setContentBySlug('dhl_international_countries', ['FR'], 'object'); expect((new DhlProductResolver)->getSupportedInternationalCountries())->toBe(['FR']); } finally { Setting::whereSlug('dhl_international_countries')->delete(); if ($createdSettingsTable) { Schema::drop('settings'); } } }); it('keeps an intentionally empty saved DHL international country list', function () { $createdSettingsTable = ensureDhlSettingsTableForResolverTest(); try { config([ 'dhl.config_source' => 'env', 'dhl.international_countries' => ['AT'], ]); Setting::whereSlug('dhl_international_countries')->delete(); Setting::setContentBySlug('dhl_international_countries', [], 'object'); expect((new DhlProductResolver)->getSupportedInternationalCountries())->toBe([]); } finally { Setting::whereSlug('dhl_international_countries')->delete(); if ($createdSettingsTable) { Schema::drop('settings'); } } }); it('normalizes configurable international countries', function () { expect(DhlProductResolver::normalizeCountryCodeList([' at ', 'DE', 'XX', 'FR', 'AT']))->toBe(['AT', 'FR']); }); function ensureDhlSettingsTableForResolverTest(): bool { if (Schema::hasTable('settings')) { return false; } Schema::create('settings', function (Blueprint $table): void { $table->increments('id'); $table->string('slug')->index(); $table->string('type')->nullable(); $table->json('object')->nullable(); $table->text('full_text')->nullable(); $table->text('text')->nullable(); $table->integer('int')->nullable(); $table->timestamps(); }); return true; } it('describes DHL product scope for preflight checks', function () { $resolver = new DhlProductResolver; expect($resolver->getProductScope('V01PAK'))->toBe('national') ->and($resolver->getProductScopeLabel('V01PAK'))->toBe('DHL Paket National') ->and($resolver->getProductScope('V53WPAK'))->toBe('international') ->and($resolver->getProductScopeLabel('V53WPAK'))->toBe('DHL Paket International'); }); it('rejects domestic products for international shipments when explicitly selected', function () { (new DhlProductResolver)->resolveProductCode('AT', 'V01PAK'); })->throws(InvalidArgumentException::class, 'Produkt V01PAK ist fuer DHL-Versand in das Zielland AT nicht erlaubt.'); it('does not fallback unknown countries to Germany', function () { (new DhlProductResolver)->toDhlCountryCode('XX'); })->throws(InvalidArgumentException::class, 'DHL-Laendercode XX wird nicht unterstuetzt.'); it('requires a billing number for the resolved product', function () { (new DhlProductResolver)->assertBillingNumber('V53WPAK', null); })->throws(InvalidArgumentException::class, 'Keine DHL-Abrechnungsnummer fuer Produkt V53WPAK konfiguriert.'); it('maps legacy product codes onto the codes the DHL API accepts', function () { $resolver = new DhlProductResolver; expect($resolver->normalizeProductCode('V53PAK'))->toBe('V53WPAK') ->and($resolver->normalizeProductCode('v53pak'))->toBe('V53WPAK') ->and($resolver->normalizeProductCode('V01PAK'))->toBe('V01PAK') ->and($resolver->normalizeProductCode(null))->toBeNull(); }); it('accepts the legacy international code without rejecting the shipment', function () { expect((new DhlProductResolver)->resolveProductCode('AT', 'V53PAK'))->toBe('V53WPAK') ->and((new DhlProductResolver)->getProductScope('V53PAK'))->toBe('international'); }); it('offers legacy setting slugs as fallback candidates for a product', function () { $resolver = new DhlProductResolver; expect($resolver->getProductCodeCandidates('V53WPAK'))->toBe(['V53WPAK', 'V53PAK']) ->and($resolver->getProductCodeCandidates('V53PAK'))->toBe(['V53WPAK', 'V53PAK']) ->and($resolver->getProductCodeCandidates('V01PAK'))->toBe(['V01PAK']); }); it('blocks destinations outside the EU customs union with an actionable message', function () { (new DhlProductResolver)->resolveProductCode('CH', null, 'V01PAK'); })->throws( InvalidArgumentException::class, 'DHL-Versand in das Zielland CH benoetigt Zolldaten (ExportDocument/CN23), die das Modul noch nicht erzeugt.' ); it('keeps customs-required countries out of the supported list even when configured', function () { $previousConfigSource = config('dhl.config_source'); $previousCountries = config('dhl.international_countries'); config([ 'dhl.config_source' => 'env', 'dhl.international_countries' => ['AT', 'CH', 'GB'], ]); try { $resolver = new DhlProductResolver; expect($resolver->getSupportedInternationalCountries())->toBe(['AT']) ->and($resolver->getAllowedProductCodesForCountry('CH'))->toBe([]); } finally { config([ 'dhl.config_source' => $previousConfigSource, 'dhl.international_countries' => $previousCountries, ]); } });