
# Test kiti ile çevrimdışı test etme

Her sunucu SDK'sının (Go, TypeScript ve Python) birim testleri ve CI için bir `testing` modülü vardır. Bu modül, sabit bir test anahtarıyla imzalanmış erişim belirteçleri ve kimlik belirteçleri üretir, yalnızca bu belirteçleri kabul eden bir belirteç doğrulayıcı oluşturur, ağ erişimi gerektirmeden çalışır, örnek olay yükleri sunar ve webhook teslimlerini herkese açık bir test sırrıyla imzalar. Testleriniz KeenAuth ortamı olmadan, kimlik bilgisi olmadan ve internet bağlantısı olmadan çalışır.

## Nasıl çalışır

- **Sabit, herkese açık bir test anahtarı.** Test belirteçleri ES256 ile ve `keenauth-test-1` anahtar kimliğiyle imzalanır. Anahtar bilinçli olarak herkese açıktır: özel kısmı yayımlanmış bir etiketten türetilir ve herkese açık anahtar kümesi her SDK'nın içindedir. Bu anahtara başka hiçbir yerde güvenmeyin.
- **Var olamayacak bir veren.** Test belirteçleri `https://test.keenauth.invalid` verenini taşır. `.invalid` üst düzey alan adı ayrılmıştır (RFC 2606) ve hiçbir zaman çözümlenmez; bu nedenle hiçbir gerçek ortam bu verene sahip olamaz.
- **Açık test modu.** SDK doğrulayıcısı bu veren değerini yalnızca kodda test modu etkinleştirildiğinde kabul eder. Normal yöntemle yapılandırılmış bir doğrulayıcı, `.invalid` altındaki her veren değerini bir yapılandırma hatası olarak reddeder. Böylece üretim doğrulayıcısı, veren yanlışlıkla test verenine ayarlansa bile bir test belirtecini kabul edemez. Test anahtarıyla imzalanmış ama gerçek veren değerini taşıyan bir belirteç de reddedilir, çünkü ortamınız farklı anahtarlar yayımlar.
- **Ağ erişimi yok.** Test modunda doğrulayıcı test anahtarını bellekten alır ve hiçbir şey getirmez. Yalnızca test verenini kabul eder.

Test modu diğer kontrolleri zayıflatmaz: süresi dolmuş belirteçler, yanlış hedef kitle, değiştirilmiş yük, eksik `jti` ve DPoP'ye bağlı ama kanıtı olmayan bir belirteç, üretimdekiyle aynı şekilde reddedilir. Tam liste için [API'nizde belirteçleri doğrulama](/docs/sdk/token-verification) sayfasına bakın.

Test modunu yalnızca test kodunda kullanın. Test belirteçlerini herkes imzalayabilir, dolayısıyla bu modu etkinleştiren bir servis sahte belirteçleri kabul ederdi.

## Go

Paket `keenauthtest` adındadır ve `net/http/httptest` tarzını izler.

```go
import (
	"github.com/keenauth/keenauth-go/keenauthtest"
	"github.com/keenauth/keenauth-go/verify"
)

func TestOrdersAPI(t *testing.T) {
	v, err := keenauthtest.NewVerifier(verify.Config{Audience: "https://api.acme.example"})
	if err != nil {
		t.Fatal(err)
	}
	token, err := keenauthtest.AccessToken("https://api.acme.example",
		keenauthtest.WithClaims(map[string]any{"scope": "orders:read", "org_id": "org_01m3vnbwg0fvav6zw6w7v3st0r"}))
	if err != nil {
		t.Fatal(err)
	}
	// Pass v to the handler under test and send "Authorization: Bearer " + token.
	claims, err := v.VerifyAccessToken(context.Background(), token, nil)
	if err != nil || claims.Scope != "orders:read" {
		t.Fatal(err)
	}
}
```

`keenauthtest.IDToken(clientID, ...)` bir kimlik belirteci üretir. `WithLifetime` geçerlilik süresini değiştirir (negatif bir değer süresi dolmuş bir belirteç üretir) ve `WithIssuedAt` düzenlenme zamanını ayarlar. `WithClaims` içinde `nil` değeri bir talep alanını kaldırır. Paketi kullanmadan `verify.Config{TestingMode: true}` değerini kendiniz ayarlayabilirsiniz.

## TypeScript

Modül `@keenauth/node/testing` alt yoludur.

```ts
import { createTestVerifier, mintAccessToken } from "@keenauth/node/testing";

const verifier = createTestVerifier({ audience: "https://api.acme.example" });
const token = mintAccessToken("https://api.acme.example", {
  claims: { scope: "orders:read", org_id: "org_01m3vnbwg0fvav6zw6w7v3st0r" },
});

const claims = await verifier.verifyAccessToken(token);
// claims.subject, claims.scope, claims.claims.org_id
```

`mintIdToken(clientId, ...)` bir kimlik belirteci üretir. Seçenekler `claims` (`null` değeri bir talep alanını kaldırır), `lifetimeSeconds` ve `issuedAt`'tir. Modülü kullanmadan `createVerifier` fonksiyonuna `testing: true` geçebilirsiniz.

## Python

Modül `keenauth.testing` olup doğrulayıcı gibi `verify` ek paketini gerektirir.

```python
from keenauth import testing

verifier = testing.verifier(audience="https://api.acme.example")
token = testing.mint_access_token(
    "https://api.acme.example",
    claims={"scope": "orders:read", "org_id": "org_01m3vnbwg0fvav6zw6w7v3st0r"},
)

claims = verifier.verify_access_token(token)
# claims.subject, claims.scope, claims.claims["org_id"]
```

`testing.mint_id_token(client_id, ...)` bir kimlik belirteci üretir. Anahtar kelime argümanları `claims` (`None` değeri bir talep alanını kaldırır), `lifetime_seconds` ve `issued_at`'tir. Modülü kullanmadan `VerifierConfig` içine `testing=True` geçebilirsiniz.

## Varsayılan talep alanları

| Talep | Erişim belirteci | Kimlik belirteci |
|---|---|---|
| `iss` | `https://test.keenauth.invalid` | `https://test.keenauth.invalid` |
| `sub` | `usr_01m3vnbwg0e8mbe7h7xxp6rmkc` | `usr_01m3vnbwg0e8mbe7h7xxp6rmkc` |
| `aud` | geçtiğiniz hedef kitle | geçtiğiniz istemci kimliği |
| `client_id` | `app_01m3vnbwg0fqba5ntanfd63s8n` | — |
| `iat`, `exp` | şu an, şu an + 3600 saniye | şu an, şu an + 3600 saniye |
| `jti` | rastgele | — |
| `auth_time` | — | `iat` ile aynı |

Erişim belirteci başlığında `typ: at+jwt` bulunur (RFC 9068). `scope`, `org_id`, `permissions`, `acr`, `amr` veya `auth_time` gibi API'nizin okuduğu talepleri ekleyin. Kimlik belirteçleri için kodunuz bir `nonce` kontrol ediyorsa bu talebi ekleyin.

## Örnek olaylar

Kit, yaygın olayların örnek yüklerini içerir: `user.created`, `user.updated`, `user.deleted`, `session.created`, `session.revoked`, `organization.created` ve `organization_membership.created`. Bunlar KeenAuth'un gönderdiği olaylarla aynı zarfı ve şemaları kullanır; varsayılan ince modda çalışırlar: tanımlayıcılar, durum ve değişen alan adları içerir, kişisel veri içermezler. Olay işleyicilerinizi test etmek için bunları kullanın.

- Go: `keenauthtest.Event("user.created")` JSON'u döndürür; `keenauthtest.EventTypes()` türleri listeler.
- TypeScript: `sampleEvent("user.created")` ve `sampleEventTypes()`.
- Python: `testing.sample_event("user.created")` ve `testing.event_types()`.

## Webhook'ları test etme

Kit, bir payload'ı tıpkı bir KeenAuth webhook teslimi gibi imzalar (Standard Webhooks v1): ham gövdeyi ve `webhook-id`, `webhook-timestamp` ve `webhook-signature` başlıklarını döndürür. İşleyiciniz bunu, üretimde çalışan aynı kod olan SDK'nın webhook doğrulayıcısıyla doğrular. Yalnızca sır farklıdır.

- **Herkese açık bir test sırrı.** Varsayılan olarak kit, yayımlanmış `KeenAuth offline test kit webhook secret (public, TEST ONLY)` etiketinden türetilen sabit bir `idp_whsec_` test sırrıyla imzalar. Bunu herkes hesaplayabilir, bu nedenle gerçek bir uç noktaya asla yapılandırmayın. Testlerde doğrulayıcınızı bu sırla yapılandırın veya yardımcı fonksiyona kendi sırrınızı geçin.
- **Gerçek teslimle aynı.** `webhook-id`, olayın `id` değeridir (payload'ta yoksa rastgele bir `msg_` kimliği) ve aksini ayarlamadığınız sürece `webhook-timestamp` şu andır. Gövde, geçtiğiniz şekliyle olduğu gibi gönderilir.
- **Tekrar oynatma ve kurcalama.** Handler'ın 5 dakikalık pencere dışındaki teslimleri reddettiğini test etmek için eski bir zaman damgasıyla imzalayın; hatalı imzayı reddettiğini test etmek için imzalamadan sonra gövdeyi değiştirin.

### Node

```ts
import { Webhook, WebhookVerificationError } from "@keenauth/node/webhooks";
import { sampleEvent, signWebhook, testWebhookSecret } from "@keenauth/node/testing";

const webhook = new Webhook(testWebhookSecret()); // in production: process.env.KEENAUTH_WEBHOOK_SECRET

const delivery = signWebhook(sampleEvent("user.created"));
const event = webhook.verify(delivery.body, delivery.headers); // the parsed event

// A delivery from an hour ago is refused.
const old = signWebhook(sampleEvent("user.created"), { timestamp: Math.floor(Date.now() / 1000) - 3600 });
try {
  webhook.verify(old.body, old.headers);
} catch (e) {
  // e instanceof WebhookVerificationError, e.kind === "timestamp"
}
```

`signWebhook(payload, options)` bir dize, bir `Uint8Array` veya bir nesne alır (`JSON.stringify` ile serileştirilir). Seçenekler `secret`, `id` ve `timestamp`'tir (Unix saniyesi veya bir `Date`). Bir HTTP işleyicisini test etmek için `delivery.body` değerini ham istek gövdesi olarak, `delivery.headers` değerini ve `Content-Type: application/json` başlığını göndərin.

### Python

```python
import time

from keenauth import testing
from keenauth.webhooks import WebhookTimestampError, WebhookVerifier

verifier = WebhookVerifier(testing.webhook_secret())  # in production: os.environ["KEENAUTH_WEBHOOK_SECRET"]

delivery = testing.sign_webhook(testing.sample_event("user.created"))
verifier.verify(delivery.body, delivery.headers)

# A delivery from an hour ago is refused.
old = testing.sign_webhook(testing.sample_event("user.created"), timestamp=int(time.time()) - 3600)
try:
    verifier.verify(old.body, old.headers)
except WebhookTimestampError:
    pass
```

`testing.sign_webhook(payload, secret=None, msg_id=None, timestamp=None)` bayt dizisi, dize veya sözlük alır (sıkıştırılmış JSON olarak serileştirilir) ve `delivery.body` (bayt) ile `delivery.headers` (sözlük) değerlerini döndürür. Flask veya FastAPI test istemcisiyle `delivery.body` değerini ham gövde, `delivery.headers` değerini başlık olarak gönderin.

### Go

```go
import (
	"net/http"
	"net/http/httptest"

	"github.com/keenauth/keenauth-go/keenauthtest"
	"github.com/keenauth/keenauth-go/webhook"
)

func TestWebhookHandler(t *testing.T) {
	v, err := webhook.NewVerifier([]string{keenauthtest.WebhookSecret()}) // in production: os.Getenv("KEENAUTH_WEBHOOK_SECRET")
	if err != nil {
		t.Fatal(err)
	}
	body, err := keenauthtest.Event("user.created")
	if err != nil {
		t.Fatal(err)
	}
	delivery, err := keenauthtest.SignWebhook(body)
	if err != nil {
		t.Fatal(err)
	}
	req, err := delivery.Request("http://localhost/webhooks/keenauth")
	if err != nil {
		t.Fatal(err)
	}
	rec := httptest.NewRecorder()
	newWebhookHandler(v).ServeHTTP(rec, req) // your handler, which calls v.Verify(r.Header, body)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d", rec.Code)
	}
}
```

`keenauthtest.SignWebhook(payload, ...)` `delivery.Body` ve `delivery.Header` değerlerini döndürür; `delivery.Request(url)` `Content-Type: application/json` başlığıyla POST isteği oluşturur. Seçenekler `WithWebhookSecret`, `WithWebhookID` ve `WithWebhookTimestamp`'tir.

## Kitin yapmadıkları

- KeenAuth'u taklit etmez. Oturum açma akışları, belirteç düzenleme kuralları, hız sınırları ve diğer tüm sunucu davranışları yalnızca barındırılan `development` ortamınızda gerçektir; yayın öncesinde entegrasyon testlerinizi orada çalıştırın.
- Test saati yoktur. Zamanı kontrol etmek için doğrulayıcıya kendi saatinizi verin (`Now`, `now`) ve belirtecin düzenlenme zamanını ayarlayın.
- DPoP kanıtları oluşturmaz. `cnf.jkt` talebi ekleyebilirsiniz, ancak bu durumda kanıtı kendiniz imzalamanız gerekir.
- Webhook'ları teslim etmez. Gerçek teslimler, yeniden denemeler ve olay seçimi yalnızca barındırılan `development` ortamınızda gerçektir.
