#!/usr/bin/env python3
"""Tutorial for kinduff/ollama-mock, commit 257cfde. Python 3, stdlib only.
Run against the dedicated mock on 127.0.0.1:11555. Not a model-quality test.
This script replaces mock faults; use a disposable test instance.
"""
import json
from urllib.request import Request, build_opener, ProxyHandler
from urllib.error import HTTPError

BASE = 'http://127.0.0.1:11555'
HTTP = build_opener(ProxyHandler({}))  # Never route this local test via a proxy.


def request(path, data=None, method=None):
    raw = None if data is None else json.dumps(data).encode()
    req = Request(BASE + path, raw, {'Content-Type': 'application/json'}, method=method)
    try:
        with HTTP.open(req, timeout=10) as response:
            return response.status, response.read().decode()
    except HTTPError as error:
        return error.code, error.read().decode()


def require(ok, label):
    if not ok:
        raise RuntimeError('FAIL: ' + label)
    print('PASS: ' + label)


def chat(model='qwen3:8b', stream=False):
    return {'model': model, 'stream': stream, 'think': False,
            'messages': [{'role': 'user', 'content': 'Explain a retry policy briefly.'}]}


def run():
    status, raw = request('/_mock/state')
    state = json.loads(raw)
    require(status == 200 and isinstance(state, dict) and 'faults' in state,
            'dedicated mock endpoint')
    request('/_mock/faults', {}, 'PUT')
    try:
        status, raw = request('/api/chat', chat())
        first = json.loads(raw)
        require(status == 200 and first.get('done') is True and first['message']['content'],
                'normal response completes')
        status, raw = request('/api/chat', chat())
        require(status == 200 and json.loads(raw)['message']['content'] == first['message']['content'],
                'same prompt returns same content (not whole-response timing)')
        status, raw = request('/api/chat', chat(stream=True))
        chunks = [json.loads(line) for line in raw.splitlines() if line.strip()]
        require(status == 200 and chunks[-1].get('done') is True and not any('error' in c for c in chunks),
                'normal NDJSON stream ends with done=true')
        status, _ = request('/api/chat', chat('not-installed:1b'))
        require(status == 404, 'uninstalled model returns 404')
        status, _ = request('/api/chat', chat('nomic-embed-text'))
        require(status == 400, 'embedding model cannot generate')
        request('/_mock/faults', {'models': {'qwen3:8b': {'status': 503, 'error': 'test busy'}}}, 'PUT')
        status, raw = request('/api/chat', chat())
        require(status == 503 and 'test busy' in raw, 'injected 503 is observable')
        request('/_mock/faults', {'models': {'qwen3:8b': {'abort_after': 2}}}, 'PUT')
        status, raw = request('/api/chat', chat(stream=True))
        chunks = [json.loads(line) for line in raw.splitlines() if line.strip()]
        require(status == 200 and any('error' in c for c in chunks)
                and not any(c.get('done') is True for c in chunks),
                'HTTP 200 can still contain a failed stream')
    finally:
        request('/_mock/faults', {}, 'PUT')
    print('Completed: this verifies mock behavior, not your app or LLM answer quality.')


if __name__ == '__main__':
    run()
