Förproduktions-testplan för ett bokförings-API
En handbok i tillförlitlighet för utvecklingsteam: dubbelkrivningar, återanvändning av idempotensnycklar, webhook-signaturer, händelser i fel ordning, decimalbelopp, behörighetsomfång, periodlås och återställning.
En 201 Created från ett bokförings-API säger dig nästan ingenting. Det bekräftar att servern svarade. Det bekräftar däremot inte att det exakt finns en faktura, att debet är lika med kredit, att verifikationsnumret tilldelades en enda gång, eller att ett omsök efter en bruten anslutning inte bokför en andra kopia. De flesta dubbletter i produktionsbokföringen uppstår i det glappet: en ändring (mutation) som har gått igenom på servern medan klienten satt med ett okänt utultat och i tysthet gjorde om anropet med en ny nyckel.
Denna plan är till för team som utvärderar ett bokförings-API innan de kopplar upp det mot riktiga räkenskaper. Den fokuserar på felbeteenden, inte på lyckade scenarier. Varje avsnitt nedan anger vad som ska testas, vad som ska verifieras (assert) och vad som räknas som godkänt. Implementeringskontexten här är Nordlet, vars API använder begränsade bearer-nycklar (scoped bearer keys), belopp som decimalsträngar, idempotensnycklar och signerade webhooks. För exakt slutpunktssyntax och svarskontrakt, läs sidorna API-konventioner och Kom igång snarare än att lita på antaganden.
Vad ”godkänt” faktiskt innebär
Ett test bevisar att en bokföringsintegration är säker endast när det verifierar semantik, inte statuskoder. För varje scenario bör sviten bekräfta:
- Affärsmässig sidof效应 (side effect): exakt en kund, faktura, verifikation, nummertilldelning eller betalningsövergång finns.
- Bokföringsmässig integritet: debet är lika med kredit, och monetär precision överlever serialisering.
- Uppspelningsbeteende: ett omsök returnerar det ursprungliga resultatet istället för en ny post.
- Beständighet (durability): audit-poster och webhook-händelser existerar när åtgärden väl har committats.
- Fel utan sidofeffekter: nekade begäran förbrukar inga verifikationsnummer och skriver inga partiella huvudboksrader.
- Återställning: ett tillfälligt fel kan köras om utan dubblering eller permanent förlust.
Googles riktlinjer för API:er drar gränsen tydligt: endast åtgärder vars upprepade körning lämnar samma slutgiltiga tillstånd är säkra att köra om, och ett API bör identifiera vilka åtgärder som är idempotenta i stället för att köra om alla misslyckanden blint.
Sätt upp en slängbar och observerbar miljö
Använd ett sandlådeföretag, inte produktionsbokföringen. Ett Nordlet-sandlådeföretag använder samma moduler och API-beteende som ett riktigt företag och märks upp som testdata, vilket gör det säkert för volymtester av dubbletter och fel. Den flaggan går inte att ändra efter skapandet, så riktiga räkenskaper kan inte i efterhand omklassificeras som testdata. Observera att sandlådan mäts och faktureras exakt som användning i ett riktigt företag, så dimensionera dina volymtester utifrån den prissättning du har.
Innan du skriver något test bör du förbereda:
- Ett dedikerat sandlådeföretag med
isSandbox: true. - En test-API-nyckel som enbart är begränsad till de moduler som testas.
- En webhook-slutpunkt eller lokal tunnel som bevarar rå-requestkroppen (raw request body).
- Inmatade (seedade) kunder, leverantörer och en känd kontoplan.
- Minst två bokföringsperioder: en öppen, en låst.
- En testklocka eller datumfixture om implementationen har stöd för det.
- Ett lokalt lager som registrerar varje anropsförsök, idempotensnyckel, svarshash, webhook-leverans och avstämningsresultat.
Kör inte dubblettstester mot delad staging-data. En villfarelse som en kvarlämnad kund eller faktura från en tidigare körning gör senare verifieringar tvetydiga.
Din fixture för varje ändring bör fånga upp metod, slutpunkt, fullständig kropp, idempotensnyckel, nyckelns omfång, ett klient-korrelations-ID, start- och sluttidsstämplar, svarsstatus, headers, kropp, request-ID och huruvida anslutningen bröts innan svaret anlände. Använd fasta decimalsträngar som "75.0000" för belopp. Nordlet representerar monetära värden som decimalsträngar med upp till fyra decimaler, och dess exempelfaktura använder det formatet.
Den andra halvan av installationen är felinjektering. Din testrigg bör kunna fördröja ett svar, bryta en TCP-anslutning efter att servern tagit emot begäran, returnera syntetiska 429/500/502/503/504-svar, returnera en non-2xx från webhook-mottagaren, leverera samma webhook två gånger, kasta om händelser i fel ordning, korrumpera ett byte i en webhook-kropp, ändra en idempotensnyckels nyttoelast (payload) men behålla nyckeln, köra två identiska anrop samtidigt, samt låsa en period mellan installation och bokföring. Dessa scenarier är värda mer än hundra lyckade exempel.
Lås kontraktet innan du skriver loggik för omsök
Generera eller hämta OpenAPI-schemat och jämför det mot din klient. Nordlet publicerar sin OpenAPI-specifikation i API-referensen och genererar sina typade SDK:er från samma specifikation, så schemastyrda tester slår handskrivna gissningar.
Verifiera obligatoriska fält och typer, decimalsträngsfält, datum- och tidsstämplingsformat, tillåtna åtgärder och omfång, felmeddelandets struktur (error envelope), scheman för webhook-händelser, vilka ändringar som accepterar en idempotensnyckel samt vilka svarsfält som identifierar det skapade objektet.
Godkännandekriterier. Klienten avvisar ogiltiga nyttoalaster lokalt där schemat tillåter det. Serverns svar matchar det dokumenterade schemat. Ogiltiga decimaler, felaktiga datum och saknade obligatoriska fält ger strukturerade valideringsfel. Inga omsök görs för deterministiska valideringsfel.
Felläge att fånga upp. En klient tolkar "121.0000" till ett binärt flyttal (float) och serialiserar ett något annorlunda belopp senare. Heltal går igenom; momsrader och fraktionella kvantiteter misslyckas. Testa pengar som strängar från början till slut.
Auktorisering och klientisolering (tenant isolation)
Använd separata nycklar för läsning och skrivning. Testa sedan hela matrisen: saknad nyckel, ogiltig nyckel, giltig nyckel med otillräckligt modulomfång, giltig nyckel med rätt omfång, en nyckel som tillhör ett annat företag samt ett försök att läsa ett objekt från ett annat företag.
Verifieringar:
- Saknade eller ogiltiga autentiseringsuppgifter ger
401. - En giltig nyckel som saknar behörighet ger
403. - Ingen bokföringsmässig sidofeffekt skapas för något av felet.
- En lyckad begäran kan varken läsa eller ändra ett annat företags poster.
- Säkerhetsfel görs aldrig om automatiskt.
Godkännandekriterium. Du kan bevisa både att auktoriseringen är korrekt och att klientisoleringen fungerar med efterföljande läsningar, inte bara genom statuskoder.
Bevisa en ren transaktion
Kör ett litet, deterministiskt flöde: skapa eller hitta en testkund, skapa en faktura med decimalsträngsbelopp, utfärda den, läs fakturan och dess bokföringsmässiga effekt, läs audit-posten samt ta emot webhooken. Nordlets Kom igång-exempel visar att fakturautfärdande tilldelar ett glappfritt nummer, kontrollerar att perioden är öppen samt bokför en balanserad verifikation i samma transaktion.
Verifiera att fakturan når det förväntade tillståndet, att verifikationsnumret tilldelas exakt en gång, att debet är lika med kredit, att moms och radsummor matchar den förväntade avrundningen (half-up), att audit-posten namnger aktören och ändringen, att webhooken slutligen levereras samt att API-svaret och en uppföljande läsning är överens om faktura-ID och tillstånd.
En distinktion som är värd att hålla fast vid: att en webhook anländer är inte ett bevis på att ändringen har gått igenom. API-svaret plus en läsning är dina primära transaktionsverifieringar. Webhooken bevisar att nedströmsystem har notifierats, och den testas separat.
Idempotens: samma nyckel, samma kropp
Generera en nyckel per logisk affärsåtgärd, inte en nyckel per nätverksförsök. Skicka ändringen med nyckel K och kropp B, spara det skapade ID:t och skicka sedan exakt samma begäran igen.
Verifiera att det andra svaret representerar samma åtgärd, att objekt-ID:t är identiskt samt att det finns ett affärsobjekt, en bokföringspost, ett verifikationsnummer och en finansiell effekt. Det uppspelta svaret bör bära den dokumenterade uppspelningsheadern (i Nordlets fall x-idempotent-replay: true), och spårbarhetsloggen (audit trail) bör inte visa någon andra ändring.
Två lagringsregler förändrar vad din omsöksloop (retry loop) bör förvänta sig, så testa dem direkt: ett lagrat 4xx-svar spelas upp som ett vanligt svar, medan ett 5xx-svar inte lagras alls, vilket innebär att ett omsök efter ett serverfel kör om åtgärden snarare än att spela upp den. Nycklar löper också ut (i Nordlet efter 24 timmar), vilket sätter en gräns för hur länge en uppspelning är tillgänglig.
Nordlets dokumentation lovar att tillägg av headern Idempotency-Key förhindrar att en begäran som körs om skapar en dubblett, och dess API-konventioner definierar den exakta uppspelningsheadern och kravet på exakt likadana svar på bytenivå. Principen matchar Stripes publicerade modell: en nyckel identifierar den logiska åtgärden, upprepningar returnerar det lagrade resultatet, och en nyckel som återanvänds med andra parametrar avvisas snarare än att ändras i tysthet.
Godkännandekriterium. Exakt en finansiell tillståndsövergång existerar oavsett hur många gånger klienten skickar samma begäran inom uppspelningsfönstret.
Idempotens: samma nyckel, ändrad kropp
Skicka den ursprungliga begäran med nyckel K och kropp B1, följt av en andra begäran med nyckel K och kropp B2 där minst ett belopp, en kund, ett datum eller en rad skiljer sig åt.
Förväntat resultat: begäran avvisas med det dokumenterade felet för återanvändning av nyckel (Nordlet returnerar 422 idempotency_key_reuse), den ursprungliga posten förblir oförändrad, ingen andra post, inget nummer eller audit-ändring visas, och klienten ”fixar” det inte genom att generera en ny nyckel. Att i tysthet acceptera B2 skulle göra omsöksnycklar osäkra.
Samtidiga dubbla anrop (concurrent duplicate requests)
Skicka två identiska begäran med samma nyckel exakt samtidigt. Beroende på det dokumenterade beteendet körs den ena medan den andra får en pågående-konflikt (Nordlet returnerar 409 idempotency_in_progress) eller så observerar båda till slut samma resultat.
Verifiera att båda anropen inte kan skapa oberoende finansiella effekter, att som mest ett verifikationsnummer tilldelas, att en tillfällig konflikt körs om med samma nyckel i stället för en ny, att den slutliga resursen är komplett och läsbar samt att idempotensposten inte fastnar permanent.
Felläge. Ett omsöksbibliotek som skapar ett nytt UUID per försök förvandlar en logisk åtgärd till flera oberoende skrivningar och slår undan benen för idempotensen helt.
Det osäkra utfallet efter en bruten anslutning
Detta är det enskilt viktigaste produktionstesten. Skicka en ändring med nyckel K, låt servern committa den, bryt anslutningen innan svaret anländer, registrera utfallet som okänt, gör sedan ett omsök med samma nyckel K och stäm av genom att läsa resurs- och audit-data.
Verifiera att omsöket returnerar det ursprungliga eller det dokumenterade uppspelningssvaret, att exakt en finansiell sidofeffekt existerar, att klienten markerar åtgärden som klar först efter ett lyckat omsök eller avstämningsläsning, samt att ingen ny nyckel genereras. En timeout betyder inte att ”ingenting hände”. Stripe beskriver idempotens precist som ett skydd mot anslutningsfel där servern kan ha bearbetat en ändring innan klienten hann ta emot svaret.
En konservativ omsökspolicy
Beslut om omsök beror både på felet och på om åtgärden är idempotent, inte enbart på HTTP-metoden.
Ofta omsöksbart, samma nyckel behålles: anslutningsåterställning eller timeout efter en idempotent ändring, 408, 429 efter att ha följt retry-after, samt 502/503/504. Ett dokumenterat pågående idempotenssvar kan köras om efter en kort fördröjning.
Inte omsöksbart utan avstämning: 400/422 valideringsfel, 401/403 autentiseringsfel, 404 för en saknad resurs, affärsmässiga 409-konflikter som inte är kopplade till en pågående nyckel, en ändrad nyttoelast på en befintlig nyckel samt en periodlåsningsvägran.
Verifiera begränsad exponentiell backoff med jitter, explicita maxförsök och tidsgränser, retry-after som respekteras för hastighetsbegränsningar (rate limits), samma nyckel som behålls vid omsök av en åtgärd, en ny nyckel för varje ny åtgärd samt loggar som skiljer det ursprungliga försök från omsök.
Testa hastighetsbegränsningar separat. Överskrid den dokumenterade tröskeln (Nordlet tillåter som standard 300 anrop per minut per API-nyckel, och vissa oautentiserade slutpunkter har egna lägre gränser per IP-adress) och bekräfta att servern returnerar svar för hastighetsbegränsning, att klienten tolkar retry-after som en fördröjning snarare än ett fel, att den pausar i stället för att hamra på, samt att ett begränsat omsök återanvänder den ursprungliga nyckeln. x-ratelimit-*-headrarna rapporterar kvarvarande budget, vilket utgör en bra förhandsverifiering. Håll hastighetsbegränsningstester borta från finansiella verifieringar om inte din fixture kan skilja ett begränsat försök från ett committat.
Verifiering av webhook-signaturer
Bygg en mottagare som sparar råa byten (raw bytes). För varje händelse: fånga upp råkroppen, läs x-nordlet-signature-headern, beräkna förväntad HMAC-SHA256 med prenumerationshemligheten (secret), jämför med en konstanttidsfunktion, tolka JSON först efter verifiering, registrera leveransen och returnera ett snabbt 2xx-svar efter godkänd lagring. Nordlet signerar en HMAC över råkroppen, skickar den som sha256=<hex> och gör nya leveransförsök med exponentiell backoff. Stripes vägledning för webhooks understryker vikten av att verifiera den omodifierade råkroppen med en konstanttidsjämförelse.
Kör negativa tester: korrekt kropp och hemlighet, korrekt kropp med felaktig hemlighet, en ändrad byte i kroppen, omserialiserad JSON med ändrade blanksteg, saknad header, felaktigt formaterad signatur, en uppspelad gammal leverans om schemat inkluderar en tidsstämpel, samt en giltig signatur levererad till fel prenumeration.
Godkännandekriterier. Endast korrekt råkropp och hemlighet accepteras. Ogiltiga signaturer ger en non-2xx-kod. Ogiltiga nyttoalaster når aldrig bokföringskön. Hemligheter hålls borta från vanliga loggar. Hanteraren bekräftar först efter att händelsen har lagrats på ett beständigt sätt.
Dubbla och felaktigt ordnade webhooks
Leveranstillförlitlighet är ett separat problem från API-idempotens, och mottagaren måste också vara idempotent. Leverera samma händelse två gånger, leverera en sale_invoice.paid-händelse före en sale_invoice.issued-händelse, returnera 500 efter lagring men före svar, samt spela upp en händelse som konsumenten redan har bearbetat.
Verifiera att dubbla leveranser inte dubblerar betalningar, statusändringar eller verifikationsåtgärder, att dedupliceringsnyckeln använder leverantörens leverans- eller händelse-ID när det finns (eller en dokumenterad sammansatt nyckel, aldrig en godtycklig tidsstämpel), att händelser i fel ordning aldrig skriver över nyare tillstånd med äldre, att konsumenten kan hämta det aktuella tillståndet från API:et när ordningen inte räcker till, samt att misslyckade konsumentförsök går via en kö eller en dead letter-kanal. Stripe varnar explicit för att leveransordningen inte garanteras och rekommenderar att bearbetade händelse-ID:n sparas.
Håll slutpunkten tunn: validera formen, verifiera signaturen, spara eller köa, bekräfta, returnera 2xx. Avstämning och aviseringar hör hemma i asynkron bearbetning. Testa det genom att låta nedströmsarbete sova förbi leveranstajmouten; en korrekt mottagare bekräftar ändå efter att kön har sparats undan.
Öppna och låsta bokföringsperioder
Nordlet upprätthåller periodlås vid bokföringstillfället över manuella verifikationer, dokumentflöden och importer, så testa kontrollen på flera ställen. Skapa motsvarande dokument daterade i en öppen period, i en låst period, vid gränsen precis före låset, den första dagen i nästa öppna period samt med ett giltigt dokumentdatum men ogiltigt bokföringsdatum om API:et skiljer de två åt.
För fallet med låst period, verifiera att begäran nekas med det dokumenterade periodlåsningsfelet (en 409 conflict i Nordlet) och att inga verifikationsrader skrivs, inget verifikationsnummer förbrukas, inget dokument övergår till bokfört och ingen framgångsrik webhook skickas. Versionen med öppen period bör lyckas med samma finansiella värden.
Kapplöpningstest (race test). Starta en bokföring mot en öppen period, lås perioden mitt under pågående anrop och bekräfta att committen inte kan runda låset. Det kritiska godkännandekriteriet: en committad bokföring hamnar aldrig i en period som låstes innan transaktionen sparades.
Spårbarhet och avstämning
För varje lyckad ändring, bekräfta att affärsobjektet existerar, att bokföringsposten existerar med debet lika med kredit, att audit-posten existerar, att webhook-händelsen existerar eller går att hämta, samt att din operationspost pekar på leverantörens objekt-ID med omsök som refererar till samma logiska åtgärd.
För varje nekad ändring, bekräfta att inget partiellt dokument existerar, att inget nummer förbrukades om inte avtalet tillåter reservering, att ingen huvudbokspost existerar, att ingen framgångsrik webhook existerar och att felet bär med sig ett request-ID för felsökning. Nordlets konventioner beskriver spårbarhet för ändringar och balanserade verifikationer som plattformsgarantier, där balans säkerställs av en uppskjuten databastriggers vid commit-tiden snarare än enbart av applikationskoden. Gör om dessa garantier till efterföljande avstämningsfrågor i stället för att lita på HTTP-svaret.
Grundläggande testmatris
| Område | Teståtgärd | Förväntad verifiering | Godkännandekriterium |
|---|---|---|---|
| Autentisering | Ingen nyckel, ogiltig nyckel, otillräckligt omfång | Strukturerat 401/403; ingen sidofeffekt |
Alla nekade försök skapar ingen bokföringspost |
| Schema och pengar | Ogiltig decimal, datum, obligatoriskt fält | Fältnivåvalideringsfel | Inget omsök eller partiell skrivning sker |
| Normal bokföring | Skapa och utfärda en faktura i öppen period | Ett dokument, en balanserad verifikation, en audit-post | Tillstånd vid återsökning och huvudbok stämmer överens |
| Uppspelning med samma nyckel | Upprepa identisk ändring, samma nyckel och kropp | Ursprungligt resultat spelas upp; ingen dubblett | Objekt-ID, nummer, verifikations-effekt, audit förblir enskilda |
| Ändrad kropp, samma nyckel | Återanvänd nyckel med annat belopp | Dokumenterat avslag för nyckelåteranvändning | Ursprunglig post oförändrad; inget nytt skrivs |
| Bruten anslutning | Commit, bryt, gör omsök med samma nyckel | Uppspelat resultat | Exakt en finansiell sidofeffekt |
| Låst period | Bokför ett dokument daterat i en låst period | Avslag på grund av periodlås | Ingen verifikation, inget nummer, ingen lyckad webhook |
| Webhook-signatur | Fel hemlighet, ändrad kropp | Non-2xx från mottagare | Endast korrekt råkropp och hemlighet accepteras |
Vad jag skulle göra först
Om tiden är knapp, kör testerna i den ordning som fångar flest kostsamma buggar. Börja med omsök vid bruten anslutning, eftersom det enskilda scenariot ger upphov till de flesta verkliga dubbletterna. Därefter paret med idempotens för samma nyckel och ändrad kropp. Sedan låsta perioder, eftersom en bokföring i en stängd månad är ett revisionsproblem som uppdagas sent. Signaturverifiering och händelser i fel ordning i webhooks kommer härnäst, och det fullständiga schemat och behörighetsmatrisen kan köras kontinuerligt i CI så fort klienten stabiliseras.
Allt annat i denna plan stöder dessa fem. Få dem gröna mot ett sandlådeföretag, där avstämningsläsningar bekräftar sidofeffekterna, så vet du att integrationen är säker innan den nuddar en enda riktig huvudbok. Du kan skapa ett sandlådeföretag och en begränsad testnyckel på några minuter via Kom igång och kontrollera vilka moduler du behöver mot listan över funktioner.
FAQ
Räcker det med ett 2xx-svar för att bekräfta att en bokföringsskrivning lyckades?
Nej. En statuskod bekräftar att servern svarade, inte att exakt ett dokument, en nummertilldelning och en balanserad verifikation existerar. Varje ändringstest bör följa upp svaret med en återsökning av objektet, dess bokföringseffekt och dess audit-post. Det farliga scenariot är den omvända riktningen: en åtgärd som gick igenom medan klienten aldrig tog emot svaret alls.
Ska jag generera en ny idempotensnyckel när en begäran får timeout?
Nej, och det är det misstag som orsakar de flesta produktionsdubbletterna. Nyckeln identifierar den logiska affärsåtgärden, inte nätverksförsöket. Gör om samma sökning med samma nyckel och stäm sedan av genom att läsa resursen. Ett omsöksbibliotek som skapar ett nytt UUID per försök förvandlar en avsedd faktura till flera oberoende skrivningar.
Vad händer om jag återanvänder en idempotensnyckel med en annan nyttoelast (payload)?
Ett korrekt API avvisar den i stället för att i tysthet tillämpa den nya kroppen. Nordlet returnerar 422 idempotency_key_reuse och lämnar den ursprungliga posten oförändrad. Din klient bör behandla detta som ett programmeringsfel att åtgärda, inte som ett tillstånd att runda genom att generera en ny nyckel.
Är webhooks ett substitut för att läsa tillbaka det skapade objektet?
Nej. En webhook-leverans bevisar nedströmsnotifiering, inte att transaktionen committades på det sätt du förväntar dig, och leveransordningen garanteras inte. Verifiera signaturen över råkroppen, deduplicera på leverans- eller händelse-ID och hämta det aktuella tillståndet från API:et när händelseordningen ensam inte räcker för att avgöra vad som har ändrats.
Hur testar jag att bokföra i en låst bokföringsperiod?
Skapa motsvarande dokument i en öppen period och i en låst, och verifiera att det låsta fallet avvisas utan verifikationsrader, utan förbrukat verifikationsnummer, utan tillståndsövergång och utan lyckad webhook. Kör sedan kapplöpningsversionen: starta en bokföring mot en öppen period och lås perioden mitt i anropet. En committad bokföring får aldrig hamna i en period som låstes innan transaktionen committades.
Kan jag köra volymstester med dubbletter i ett sandlådeföretag?
Ja. Ett sandlådeföretag beter sig som ett riktigt, är uppmärkt som testdata och raderas omedelbart i stället för efter lagringsfönstret, så det är rätt plats för destruktiva tester och dubbletteri. Sandlådeanvändning mäts och faktureras fortfarande som verklig användning, så planera volymen i stället för att loopa i det oändliga.