Exploring Infinite Possibilities on FrontEnd 🚀
Burak Sağlık
Search...

Thu Aug 13 2026

AI Ajanları İçin Test Yazma: Agent-Ready Contract Testing Rehberi

AI Ajanları İçin Test Yazma: Agent-Ready Contract Testing Rehberi

🎯 Giriş: Neden "Testler Geçti" Artık Yeterli Değil?

Hazırsan başlayalım 🚀

Yıllarca testler geçti mesajını gördüğümüzde "her şey yolunda" diye nefes alıp geçtik. Geçmişte testler biz insan geliştiriciler için bir güven ağıydı — refaktör yaparken bir şey kırıp kırmadığımızı anlayan, "şu an kod çalışıyor" diyen sessiz bir arkadaştı ✅

Ama şimdi durum değişti.

Artık kodumuzu okuyan, anlayan ve değiştiren bir AI ajanı da var ortada 🤖

  • AI ajanları kodun niyeyi bilmek ister, sadece nasıl çalıştığını değil
  • "Testler geçti" demek yetmez — ajanın neleri garanti ettiğini anlayabilmesi lazım
  • Contract testing, behavior-driven naming, self-documenting assertions... bunlar artık lüks değil, zorunluluk ❗️

AI Ajanları ile çalışırken kodumuz Agent-Ready olmalı — yani testler birer sözleşme gibi davranmalı 📜

Bu yazıda testleri sadece geçmek için değil, AI'ın anlayabileceği bir sözleşme haline getirmek için nasıl yazacağımızı keşfedeceğiz 🔍


❗️ Sorun: İnsan İçin Yazılan Testler, AI Ajanları İçin Neden Yetersiz Kalıyor?

Arkadaşlar, Yazılım Mimarisi ve Test Odaklı Geliştirme konularında uzun yıllar süren alışkanlıklarımızı sorgulamamız gereken bir noktadayız 🎯

Ne oluyor burada?

İnsan bir testi okurken bağlamı (context) kafasında canlandırır:

  • createUser fonksiyonunu görür → "Kullanıcı oluşturma işlemi" der
  • expect(result).toBeTruthy() satırını okur → "İşlem başarılı olmuş" anlar
  • Yorum satırlarını, isimlendirmeleri, hatta dosya yapısını bir bütün olarak hisseder

AI ajanı ise? Sadece tokenlar, tipler ve yapı görür 🤖

"Test geçiyor" demek yetmez. Ajanın neyi test ettiğini, girdi/çıktıların ne olduğunu yapısal olarak bilmesi gerekir.


Somut bir karşılaştırma 🔁

İşte aynı senaryo için yazılmış iki test — solda insana hitap eden, sağda AI ajanı dostu versiyon:

// ❌ İnsan odaklı — "Anlamlı" ama yapısal zayıf
test('Kullanıcı oluşturulduğunda başarılı dönmeli', async () => {
  const result = await createUser({ name: 'Ahmet', email: 'ahmet@test.com' });
  expect(result).toBeTruthy();           // Ne döndü? Bilinmiyor.
  expect(result.id).toBeDefined();       // ID var mı? Tipi ne?
  // "Başarılı" demek yetiyor insana 😅
});

// ✅ AI ajanı odaklı — Şema, tipler, kontrat net
test('createUser: valid input → returns UserResponse', async () => {
  const input: CreateUserInput = { name: 'Ahmet', email: 'ahmet@test.com' };
  const result = await createUser(input);
  
  // Yapısal doğrulama — ajan bu şemayı *anlar*
  expect(result).toMatchObject<UserResponse>({
    id: expect.any(String),              // UUID string
    name: 'Ahmet',                       // Aynen girdi
    email: 'ahmet@test.com',             // Aynen girdi
    createdAt: expect.any(String),       // ISO date
    isActive: true                       // Default değer
  });
});

Farkı özetlersek ✅

Boyut İnsan Odaklı Test AI Ajanı Odaklı Test
Assertion toBeTruthy() — boolean yeterli toMatchObject<Schema>() — yapı zorunlu
Girdi/Çıktı İmplicit, isimden tahmin edilir Explicit tip tanımı (CreateUserInput, UserResponse)
Bağlam Kafada tutulur Tokenlarda, tiplerde, şemada yer alır
Yorum satırı "Başarılı olmalı" Gereksiz — kod kendini anlatır 🛠

Neden bu kadar kritik? ❗️

AI ajanları kod üretirken, refactor yaparken veya hata ayıklarken testlerinizi tek bilgi kaynağı olarak kullanır. Eğer testleriniz "boolean true döndü" diyorsa, ajan:

  • Hangi alanların döndüğünü bilmez 🤷‍♂️
  • Yanlış tipte veri üretebilir
  • Kırılgan assertion'lar yazabilir

Şema doğrulamalı, girdi/çıkış tipi net testler yazmak — bu sadece "iyi pratik" değil, ajanlarla güvenli çalışmanın şartı 🔐


Hazırsan bir sonraki bölümde bu yapısal test yazımını nasıl sistematik hale getireceğimizi (şema kütüphaneleri, contract testing, type-safe test helpers) konuşalım 🚀


🔁 Kavram: Agent-Ready Code Nedir ve Yeni Sözleşme (Contract) Nedir?

Hazırsan bu kavramı bir kahve molası süresinde bitirelim ☕️

Agent-Ready Code kısaca şu demek: Kodun, bir LLM/ajan tarafından baştan sona anlaşıp, güvenle değiştirilebilir ve genişletilebilir hale getirilmesi.

Eski dünyada "sözleşme" = fonksiyon imzasıydı. Yeni dünyada sözleşme çok daha fazlası 👇

Yeni Sözleşme Neyi Kapsıyor?

  • Girdi şeması — Ne bekliyoruz? (tip, zorunluluk, kısıtlar)
  • Çıktı şeması — Ne döneceğiz? (tip, olası varyantlar)
  • Hata durumları — Hangi kodlarla, hangi mesajlarla başarısız olabiliriz?
  • Yan etkiler (side effects) — Veritabanı yazıyor mu? Dış API çağırıyor mu? Event fırlatıyor mu?
  • Performans garantileri — Latency p95, throughput, retry politikası…

Bu sözleşme tip güvenliğiyle zorunlu kılınıyor. TypeScript'in tür sistemi, sözleşmeyi derleme zamanında bir "kanıt"a çeviriyor. LLM kod üretirken prompt'a bu şemaları (veya Zod/JSON Schema karşılıklarını) besliyorsunuz; model "sözleşmeye uyan" kod yazmak zorunda kalıyor 🎯


Pratikte Nasıl Görünüyor? 🛠

Aşağıda bir fonksiyon için Sözleşme tanımını temsil eden TypeScript bloğu var. Kopyala-yapıştır değil, mental model olarak sakla:

// 📄 Sözleşme tanımı — tek kaynak gerçeği (Single Source of Truth)
interface FunctionContract<
  TInput extends object,
  TOutput extends object,
  TErrorCode extends string,
  TSideEffect extends string
> {
  /** Girdi şeması — Zod/JSON Schema ile de ifade edilebilir */
  inputSchema: TInput;

  /** Çıktı şeması — başarı durumunda ne döner? */
  outputSchema: TOutput;

  /** Hata kodları — union type ile exhaustive check zorunlu kılınır */
  errorCodes: readonly TErrorCode[];

  /** Yan etkiler — sadece dokümantasyon değil, static analysis için de kaynak */
  sideEffects: readonly TSideEffect[];

  /** Performans SLA'si — opsiyonel ama önerilir */
  performance?: {
    latencyP95Ms: number;
    maxThroughputPerSec?: number;
    retryPolicy?: {
      maxAttempts: number;
      backoffMs: number;
    };
  };
}

// 🎯 Kullanım örneği: "Kullanıcı oluştur" fonksiyonu sözleşmesi
type CreateUserContract = FunctionContract<
  // Input
  {
    email: string;           // format: email, unique
    fullName: string;        // min: 2, max: 100
    roleId?: string;         // optional, default: 'user'
  },
  // Output
  {
    userId: string;          // UUID v4
    createdAt: string;       // ISO 8601
  },
  // Error Codes
  'EMAIL_CONFLICT' | 'INVALID_ROLE' | 'VALIDATION_FAILED',
  // Side Effects
  'DB_WRITE:users' | 'EVENT_EMIT:user.created' | 'EXTERNAL_CALL:notify.welcome'
>;

Bu Sayede Ne Oluyor? ✅

Kazanç Açıklama
LLM güvenle üretir Prompt'a CreateUserContract verildiğinde, model hata kodlarını, yan etkileri, şemaları ezberlemek zorunda kalmaz — tipi zaten biliyor.
Refactor korkusu biter Sözleşme değişirse TypeScript derleyici tüm çağrı yerlerini, testleri, mock'ları uyarır.
Gözden kaçan yan efekt yok sideEffects alanında DB_WRITE:users görürsen, "bu fonksiyon transaction içinde mi?" diye sorarsın.
SLA visibility performance.latencyP95Ms: 150 yazıyorsa, load testinde bu sayıyı assert edebilirsin.

Kısa Özet 🎯

Yeni sözleşme = Tip güvenliği + Şema + Hata kodları + Yan etkiler + SLA
Hepsi derleme zamanında kanıtlanabilir, LLM prompt'una tek bir nesne olarak beslenebilir.

Bir sonraki bölümde bu sözleşmeyi nasıl otomatik testlere, dokümantasyona ve CI kapısına bağlayacağımızı göreceğiz 🚀


🛠 Pratik: Tip Güvenliği ile AI'nin Anlayabileceği Sınırları Çizmek (Zod + TypeScript)

Tip Güvenliği ile runtime validation (Zod) arasında pratik bir bağlantı kuracağız. Bu sayede TypeScript'in compile‑time garantilerini korurken, anlaşılabilir bir şema oluşturuyoruz. AI ajanımız böylece "Bu fonksiyon email string alıyor, regex ile kontrol ediliyor ve boş olamaz" diyebilecek.


🧭 Problem nedir?

  • TypeScript compile‑time garantileri sunar, fakat runtime kontrolünü sağlamaz.
  • Zod, okunabilir bir şema tanımlar ve her istekte doğrulama yapar.
  • İkisini birleştirdiğimizde hem geliştirici deneyimi iyileşir hem de AI için kolay anlaşılabilir bir doküman ortaya çıkar.

📦 Şema + Tip Tanımı

import { z } from 'zod';

export const UserRegistrationSchema = z.object({
  // 📧 Email: string, geçerli format, boş olamaz
  email: z
    .string({ message: 'Email string olmalı' })
    .email({ message: 'Geçerli bir email adresi olmalı' })
    .nonempty({ message: 'Email boş olamaz' }),

  // 👤 Username: 3‑20 karakter arasında
  username: z
    .string({ message: 'Kullanıcı adı string olmalı' })
    .min(3, { message: 'En az 3 karakter olmalı' })
    .max(20, { message: 'En fazla 20 karakter olmalı' }),

  // 🔢 Age: isteğe bağlı, pozitif integer
  age: z
    .number({ message: 'Yaş bir sayı olmalı' })
    .int({ message: 'Yaş tam sayı olmalı' })
    .positive({ message: 'Yaş pozitif olmalı' })
    .optional()
});

/** Zod şemasından türetilen TypeScript tipi */
export type UserRegistrationInput = z.infer<typeof UserRegistrationSchema>;
  • Zod şeması fonksiyonlar, route’lar ve UI componentler için koşulları tanımlar.
  • z.infer tipi, aynı şemanın TypeScript tarafındaki tip temsilcisidir.
  • Artık hem TypeScript tip güvenliği hem de Zod’un runtime gücü mevcut.

⚙️ Fonksiyon imzasında kullanım

/** UserRegistrationInput tipiyle çalışır, Zod şemasıyla otomatik doğrulanır */
async function registerUser(data: UserRegistrationInput): Promise<void> {
  /* 1️⃣ Compile‑time kontrol: TypeScript build sırasında tipi kontrol eder */
  /* 2️⃣ Runtime kontrol: Zod her request’te şemayı doğrular */
  const parsed = UserRegistrationSchema.parse(data);

  // parsed artık garanti edilmiş bir nesnedir 🎉
  console.log(`Kayıt: ${parsed.username} (${parsed.email})`);
}
  • registerUser fonksiyonu, compile‑time garanti (TypeScript) ile runtime güvenlik (Zod) bir arada sunar.
  • AI ajanımız şemadan yola çıkarak “email alanı bir string olmalı, regex ile format kontrol ediliyor ve boş olamaz” diyebilir.

✅ Neden bu yöntem işe yarar?

  • 🔁 Çift yönlü güvenlik – Tip denetimi + veri denetimi.
  • 🎯 Okunabilir şema – AI kolayca yorumlayabilir, geliştirici bakım kolaylığı sağlar.
  • 🛡️ Runtime hataları önleme – Geçersiz veriyle karşılaştığınızda Zod anında hata verir.
  • 📦 Üretilebilirlik – Aynı şema, istemci tarafında form doğrulaması, dokumentasyon ve OpenAPI/JavaScript çıktıları için yeniden kullanılabilir.

Bu pratik kombinasyon ile Tip Güvenliğini (type safety) runtime şemasıyla birleştirerek, AI’lar (ve insanlar) işlevlerin beklentilerini kolayca anlayabilir. Artık registerUser fonksiyonu, güvenilir ve self‑dokumented bir API parçası 🎉.


🛠 Pratik: Testleri "Canlı Belgeler" Olarak Yeniden Yapılandırmak (Contract Testing / Pact)

Hazırsan başlayalım. 🛠️ Hadi bir tane consumer-driven contract testine göz atalım ve bunu nasıl "belgeleme" aracına çevirebileceğimizi görelim.

Contract Testing nedir? 🎯

  • Consumer (örn. AI ajanı veya mobil istemci) ile Provider arasında anlaşma vardır.
  • Testler canlı belgeler gibidir: provider'ın consumer'a tam olarak ne vereceğini tanımlar.
  • Sözleşme ihlali olduğunda test başarısız olur → guarantee yerine documentation.

Testleri "doğrulama"dan "belgeleme"a geçirmek 🔄

  • Test, beklenen yanıtı yazmak, sağlayıcıya istenilen yanıtı testing yaptırmak değil.
  • Açıkça belirtilen sağlayıcı state’leri (örneğin “kullanıcı bulundu”, “kullanıcı bulunamadı”) tanımlarsın.
  • Güvenilir bir refakatçi haline gelir; ekipteki herkes, hizmetlerin gerçekten ne sunduğunu anında anlayabilir.

Bir testte assert edeceklerimiz ❗️

  • Şema uyumluluğu – yanıt, tanımlanan şekle uygun mu?
  • Hata senaryoları – beklenmedik durumlar nasıl işlenir?
  • Yan etiket (side effect) yokluğu – test, consumer’ı yanlışlıkla değiştirmemeli.

Bu üç kontrol, sağlayıcıya ulaşan isteğin güvenli ve tanımlanabilir olduğunu garanti eder.

Örnek: Node.js consumer-driven contract (Pact) 🛠

Aşağıda, UserProfileProvider sağlayıcısı için iki etkileşim belgeleyen basit bir TypeScript örneği bulunmaktadır:

import { Pact } from '@pactium/pact';
import { Matcher } from '@pactium/pact/src/matchers';

const provider = new Pact({
  consumer: 'MyAIConsumer',
  provider: 'UserProfileProvider',
});

describe('User Profile API contract', () => {
  beforeAll(async () => {
    await provider.setup();
  });

  it('can document happy path', async () => {
    await provider.addInteraction({
      uponReceiving: 'a request for user profile',
      withRequest: {
        method: 'GET',
        path: '/users/{id}',
        headers: { 'Content-Type': 'application/json' },
        query: { id: '123' }
      },
      willRespondWith: {
        status: 200,
        headers: { 'Content-Type': 'application/json' },
        body: {
          id: '123',
          name: 'John Doe',
          email: 'john@example.com'
        }
      }
    });
  });

  it('documents error when user not found', async () => {
    await provider.addInteraction({
      uponReceiving: 'a request for missing user',
      withRequest: {
        method: 'GET',
        path: '/users/{id}',
        headers: { 'Content-Type': 'application/json' },
        query: { id: '999' }
      },
      willRespondWith: {
        status: 404,
        headers: { 'Content-Type': 'application/json' },
        body: {
          error: 'Not Found',
          message: 'User not found'
        }
      }
    });
  });
});

ÖzetuponReceiving sağlayıcıya yönelik isteği tanımlar; withRequest isteğin şeklini; willRespondWith ise belgelenen yanıtı içerir (status, header, body). Birkaç test, provider state’lerini açıkça kapsadığından, anlaşma canlı bir belge haline gelir.

Bunu yazılım mimarisine nasıl yansıtırız? 🏗

  • Her servis bağı (API, mesaj kuyruğu, gRPC endpoint’i) için bir contract tanımlayın.
  • Bu contract’ları docs/ veya contracts/ dizinlerinde saklayın; doğrudan kaynak kodunda değil.
  • Consumer ve provider ekipleri arasında çapraz referanslar, proje genelinde tutarlı bağımlılıklar sağlar.

pact veya Spring Cloud Contract gibi araçlar, bu sözleşmeleri okunabilir, sürdürülebilir canlı dokümanlara dönüştürerek her birinin kodunuzu tekrar tekrar doğrulamaktan kurtarır.


🔁 Süreç: CI/CD'de AI Ajanı Doğrulama Adımlarını Eklemek (GitHub Actions)

Hadi gelin "Ben yerelde test ettim, sorun yok" demekten kurtulup, pipeline'ın kendisi bizim yerimize "Agent-Ready" standardını zorlasın 🎯.
Bir contract validation adımı ekleyerek, kodun AI ajanı tarafından güvenle tüketilebilir olduğundan emin oluyoruz.

Ne yapıyoruz? 🛠

  1. Tip kontrolü (tsc) — TypeScript'in derleme anında yakaladığı hataları erken yakalıyoruz.
  2. Şema doğrulama testleri — Girdi/çıktı şemalarının (JSON Schema, Zod, vb.) tutarlı olduğunu test ediyoruz.
  3. Contract testleri (Pact verification) — Consumer-driven contract'lerin sağlayıcı tarafında da geçerli olduğunu kanıtluyoruz.
  4. (Opsiyonel) LLM tabanlı statik analiz — Kod kalitesi, güvenlik veya "agent-friendly" desenler için ek bir göz atışı.

Kural basit: Bu adımlardan herhangi biri başarısız olursa merge engellenir ⛔.
Böylece main branch'ine giren her commit, AI ajanı için hazır demektir ✅.


GitHub Actions Workflow: .github/workflows/agent-ready.yml

Aşağıdaki dosyayı repoya ekleyin. validate-contract job'ı, push ve pull request'te tetiklenir 🔁.

name: Agent-Ready Contract Validation

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  validate-contract:
    name: 🔍 Contract & Type Validation
    runs-on: ubuntu-latest
    timeout-minutes: 15

    steps:
      # 1️⃣ Node.js ortamını hazırla
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      # 2️⃣ Bağımlılıkları kur (ci modunda, lockfile'a saygılı)
      - name: Install dependencies
        run: npm ci

      # 3️⃣ Tip kontrolü — tsc --noEmit
      - name: Run TypeScript type check
        run: npm run typecheck
        # package.json'da: "typecheck": "tsc --noEmit"

      # 4️⃣ Şema doğrulama testleri
      - name: Run schema validation tests
        run: npm run test:schema
        # Örn: Jest/Vitest ile Zod/JSON Schema testleri

      # 5️⃣ Pact contract verification (provider tarafı)
      - name: Run Pact verification
        run: npm run pact:verify
        env:
          PACT_BROKER_BASE_URL: ${{ secrets.PACT_BROKER_BASE_URL }}
          PACT_BROKER_TOKEN: ${{ secrets.PACT_BROKER_TOKEN }}
          # Gizli bilgileri Settings > Secrets kısmından verin

      # 6️⃣ (Opsiyonel) LLM tabanlı statik analiz
      # - name: Run LLM static analysis
      #   run: npx @your-org/llm-code-review@latest
      #   env:
      #     OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

Bu workflow ne sağlıyor? 🤔

Adım Ne Garanti Ediyor?
typecheck Derleme anında tip hatası yok → runtime sürprizleri azalır
test:schema API girdi/çıktı şemaları tutarlı → AI ajanı "beklenen formatta" veri alır
pact:verify Consumer ile sözleşme bozulmamış → breaking change riski sıfırlanır
LLM analizi (ops.) Kod kalitesi, güvenlik, "agent-friendly" desenler için ek göz

İpucu: pact:verify adımı için Pact Broker URL/token'ını repository secret'ları olarak saklayın.
Böylece hassas bilgiler loglarda görünmez 🔐.


Sonuç: "Agent-Ready" kapısı burası 🚪

Bu workflow merge'e güvenlik kapısı görevi görür.
Geliştirici "Çalışıyor, merge edin" dese bile, pipeline "Contract bozulmuş, tip hatası var, schema uyumsuz" diyerek engeller ⛔.

Böylece AI ajanı bu kodu çektiğinde:

  • Tip güvenliği var ✅
  • Şema tutarlılığı var ✅
  • Consumer sözleşmesi bozulmamış ✅

Hazırsan bir sonraki bölümde bu adımları nasıl yerel geliştirme döngüsüne de entegre edeceğimize bakalım 🔜.


🛠 Uygulama: Bir Örnek Senaryo - Kullanıcı Kaydı Akışını Agent-Ready Hale Getirmek

Hazırsan bu bölümü tek bir User Registration akışı üzerinden baştan sona inşa edelim.
Amacımız: bir AI ajanı bu dosyaları okuyup “yeni bir alan ekle” dediğinde sıfır hata ile ne yapacağını bilebilsin 🎯


📁 Proje yapısı (tree)

src/
├─ domain/
│  ├─ user/
│  │  ├─ schema.ts          # Zod girdi / çıktı şemaları
│  │  ├─ types.ts           # Result / Either hata tipleri
│  │  └─ service.ts         # Kayıt iş mantığı
├─ tests/
│  └─ contract.test.ts      # Contract (şema) testleri
└─ index.ts                 # Uygulama giriş noktası (opsiyonel)

Not: Sadece schema.ts, service.ts ve contract.test.ts dosyalarını tam olarak göstereceğim; diğer dosyalar sadece bağlam için var.


1️⃣ src/domain/user/schema.ts — Girdi & Çıktı şemaları (Zod)

// src/domain/user/schema.ts
import { z } from "zod";

/** 1️⃣ Kullanıcı kayıt isteği — AI ajanı bu şemayı okuyup yeni alan eklerken
 *    sadece `extend` kullanacak, gerisi değişmez. */
export const RegisterInputSchema = z.object({
  email: z.string().email({ message: "Geçersiz e‑posta formatı" }),
  password: z.string().min(8, { message: "Şifre en az 8 karakter olmalı" }),
  // 👇 Yeni alan eklenecekse buraya `z.string().optional()` gibi ekleriz
});

/** 2️⃣ Başarılı kayıt sonrası dönen veri — front‑end / API sözleşmesi */
export const RegisterOutputSchema = z.object({
  id: z.string().uuid(),
  email: z.string().email(),
  createdAt: z.string().datetime(),
});

/** Tür çıkarımı — kodda `RegisterInput` / `RegisterOutput` olarak kullanılır */
export type RegisterInput = z.infer<typeof RegisterInputSchema>;
export type RegisterOutput = z.infer<typeof RegisterOutputSchema>;

Ne oldu?

  • Zod şemaları tek kaynak (single source of truth) olduk.
  • AI ajanı yeni alan eklerken RegisterInputSchema.extend({ ... }) yapar, testler otomatik güncellenir ✅

2️⃣ src/domain/user/types.ts — Result / Either hata tipi

// src/domain/user/types.ts
/** Basit Result<E, A> — Either pattern'in hafif hali */
export type Result<E, A> =
  | { ok: true; value: A }
  | { ok: false; error: E };

/** Hata kategorileri — servis katmanı bu union'u döner */
export type RegisterError =
  | { type: "ValidationError"; issues: readonly z.ZodIssue[] }
  | { type: "EmailTaken" }
  | { type: "Unexpected"; cause: unknown };

Neden bu yapı?

  • ok: true/false sayesinde pattern matching (switch) ile hata yönetimi zorunlu hale gelir ❗️
  • AI ajanı hata tiplerini genişletmek istediğinde union’a yeni type ekler, derleyici uyarır 🛠

3️⃣ src/domain/user/service.ts — Kayıt iş mantığı

// src/domain/user/service.ts
import { RegisterInput, RegisterOutput, RegisterInputSchema } from "./schema";
import { Result, RegisterError } from "./types";
import { v4 as uuidv4 } from "uuid";

/** Sahte repo — gerçek projede DB / ORM olur */
const usersDb = new Map<string, RegisterOutput>();

export async function registerUser(
  rawInput: unknown
): Promise<Result<RegisterError, RegisterOutput>> {
  // 1️⃣ Şema doğrulama — Zod parseAsync hatayı `ZodError` olarak fırlatır
  const parseResult = RegisterInputSchema.safeParse(rawInput);
  if (!parseResult.success) {
    return {
      ok: false,
      error: { type: "ValidationError", issues: parseResult.error.issues },
    };
  }
  const input: RegisterInput = parseResult.data;

  // 2️⃣ İş kuralı: e‑posta benzersiz olmalı
  const emailTaken = Array.from(usersDb.values()).some(
    (u) => u.email === input.email
  );
  if (emailTaken) {
    return { ok: false, error: { type: "EmailTaken" } };
  }

  // 3️⃣ Kayıt oluştur
  const now = new Date().toISOString();
  const newUser: RegisterOutput = {
    id: uuidv4(),
    email: input.email,
    createdAt: now,
  };
  usersDb.set(newUser.id, newUser);

  return { ok: true, value: newUser };
}

Akış özeti

  1. Zod ile güvenli parse (safeParse) → hata ValidationError olarak döner.
  2. İş kuralı kontrolü → EmailTaken.
  3. Başarılıysa Result.ok ile RegisterOutput döner.
    AI ajanı yeni bir kural eklerken sadece if bloğu yazacak, tip sistemi gerisini garanti altına alır ✅

4️⃣ tests/contract.test.ts — Contract (şema) testleri

// tests/contract.test.ts
import { describe, it, expect } from "vitest";
import { RegisterInputSchema, RegisterOutputSchema } from "../src/domain/user/schema";

describe("📄 User Registration Contract", () => {
  it("geçerli girdi şemasını karşılar", () => {
    const valid = { email: "test@example.com", password: "secret123" };
    const result = RegisterInputSchema.safeParse(valid);
    expect(result.success).toBe(true);
  });

  it("geçersiz e‑posta reddedilir", () => {
    const invalid = { email: "not-an-email", password: "secret123" };
    const result = RegisterInputSchema.safeParse(invalid);
    expect(result.success).toBe(false);
    if (!result.success) {
      expect(result.error.issues[0].message).toContain("Geçersiz e‑posta");
    }
  });

  it("çıktı şeması beklenen alanları içerir", () => {
    const sampleOutput = {
      id: "c0ffee00-dead-beef-cafe-123456789abc",
      email: "test@example.com",
      createdAt: "2024-01-01T00:00:00.000Z",
    };
    const result = RegisterOutputSchema.safeParse(sampleOutput);
    expect(result.success).toBe(true);
  });
});

Neden contract test?

  • Şema değiştiğinde (yeni alan eklendiğinde) test otomatik kırılır → CI uyarır 🚨
  • AI ajanı RegisterInputSchema.extend({ phone: z.string().optional() }) yazdığında testleri güncellemek zorunlu hale gelir, aksi takdirde build fails ⛔

🤔 Son soru: AI ajanı versiyon 2'ye nasıl yükseltir?

Bak şimdi — yeni bir phone alanı eklemek istiyorsun.

  1. schema.tsRegisterInputSchema.extend({ phone: z.string().optional() })
  2. service.tsinput.phone varsa kaydet (veya ayrı tablo).
  3. contract.test.tsphone içeren geçerli/geçersiz case'ler ekle.
  4. types.ts → hata tipleri değişmediyse dokunma.

Hepsi bu kadarı. Derleyici ve testler seni yönlendirir, sen sadece iş mantığına odaklan 🎉


🎯 Bonus Tavsiye: Ekip Kültürünü Değiştirmek - "Test Yazıyorum" De, "Sözleşme Tasarlıyorum" De

Hazırsan kültür konuşalım 🎯. Kod yazarken sözleşme (contract) düşünmek, sadece test yazmaktan çok daha güçlü bir alışkanlıktır.

Neden "Sözleşme Tasarlıyorum" demek gerekiyor?

  • Tip Güvenliği 🔒: Derleme zamanında hataları yakalar, runtime sürprizlerini engeller.
  • Agent-Ready Code 🤖: AI ajanları ve yeni ekip üyeleri, arayüzü okuyup ne bekliyorsa anlar.
  • Onboarding Hızı ⚡: "Ne döndürür bu fonksiyon?" sorusuna cevap şema içinde zaten vardır.

Pratik ipuçları ekibe kazandıralım

  1. Code Review Checklist'ine şu soruyu ekle:

    Bu değişiklik sözleşmeyi bozar mı?

  2. Contract First geliştirme alışkanlığı kur:

    • Önce girdi/çıktı şemalarını (OpenAPI, JSON Schema, Protobuf…) yaz.
    • Sonra implementasyona geç.
  3. Terminolojiye Tip Güvenliği ve Agent-Ready Code kavramlarını yerleştir.

    • Her PR’da bu kelimeleri duymaya başlasın ekip.
  4. Yeni başlayanlara (ve AI ajanlarına) “Sözleşme önce, kod sonra” mantığını anlat.

Pull Request template örneği

## Sözleşme Değişiklik Kontrolü
- [ ] Girdi/Çıktı şemaları güncellendi
- [ ] Contract testleri geçiyor
- [ ] Breaking change varsa migration planı eklendi

Bu sayede herkes “Ne değişti? Ne bozuldu? Nasıl migrate edeceğiz?” sorularını PR açıklamasından anlar.

Kapanış

Bu alışkanlık size yarın gelecek AI mesai arkadaşınız için en iyi onboarding belgesini yazmış olmanızı sağlar 🚀. Hadi bugünden başlayalım! ✨


🎯 Son Söz: Geleceğe Hazır Kod Tabanı İçin İlk Adımı At

Hazırsan, özetleyelim ve harekete geçelim 🚀

Ana noktalar hızlıca:

  • Testler artık sadece bir güven ağı değil, AI ajanları için talimat setidir 🤖
  • Tip sistemleri ve şemalar bu talimatların dilidir; açık, tutarlı ve makine okunabilir olmalı 📜
  • CI/CD pipelines, bu dilin zorunlu kılındığı ve sürekli doğrulandığı yerdir ⚙️

Sana soru:

Hangi projenizde bugün bir “Agent‑Ready” test yazmaya başlayacaksınız? 🤔

Küçük bir adım at, bir test yaz, tip tanımını netleştir, pipeline’a ekle. Böylece kod tabanın hem insan hem de geleceğin AI ortakları için anlaşılır, güvenilir ve genişletilebilir hale gelecek ✨

Hadi, ilk adımı birlikte atalım — gelecek senin kodunda başlıyor! 🌟


Bu içerik tamamen yapay zeka destekli otomasyon sistemi ile üretilmiştir.

Burak Sağlık

Burak Saglik

©2024 Desing and Developed by @Burak Sağlık

All rights reserved