WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,155 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepairJSON(t *testing.T) {
|
||||
t.Run("valid JSON unchanged", func(t *testing.T) {
|
||||
input := `{"query": "hello", "limit": 10}`
|
||||
result := RepairJSON(input)
|
||||
assert.Equal(t, input, result)
|
||||
assert.True(t, json.Valid([]byte(result)))
|
||||
})
|
||||
|
||||
t.Run("trailing comma before brace", func(t *testing.T) {
|
||||
input := `{"query": "hello", "limit": 10,}`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("trailing comma before bracket", func(t *testing.T) {
|
||||
input := `{"items": [1, 2, 3,]}`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("missing closing brace", func(t *testing.T) {
|
||||
input := `{"query": "hello"`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("missing closing bracket and brace", func(t *testing.T) {
|
||||
input := `{"items": [1, 2, 3`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("empty string returns empty object", func(t *testing.T) {
|
||||
result := RepairJSON("")
|
||||
assert.Equal(t, "{}", result)
|
||||
})
|
||||
|
||||
t.Run("truncated string value", func(t *testing.T) {
|
||||
input := `{"query": "hello world`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("nested missing closers", func(t *testing.T) {
|
||||
input := `{"outer": {"inner": [1, 2`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("comma in string not removed", func(t *testing.T) {
|
||||
input := `{"query": "hello, world"}`
|
||||
result := RepairJSON(input)
|
||||
assert.Equal(t, input, result)
|
||||
})
|
||||
|
||||
t.Run("already valid complex JSON", func(t *testing.T) {
|
||||
input := `{"queries": ["what is AI", "machine learning"], "limit": 5}`
|
||||
result := RepairJSON(input)
|
||||
assert.Equal(t, input, result)
|
||||
assert.True(t, json.Valid([]byte(result)))
|
||||
})
|
||||
|
||||
t.Run("multiple trailing commas", func(t *testing.T) {
|
||||
input := `{"a": 1, "b": [1, 2,], "c": 3,}`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
})
|
||||
|
||||
t.Run("invalid escape: regex plus (C\\+\\+)", func(t *testing.T) {
|
||||
// The LLM emits a raw regex without double-escaping — \+ is not a
|
||||
// legal JSON escape. Repair should rewrite it to \\+ so the
|
||||
// unmarshalled string is the regex pattern `C\+\+`, matching
|
||||
// literal `C++`.
|
||||
input := `{"queries": ["C\+\+"]}`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
|
||||
var parsed struct {
|
||||
Queries []string `json:"queries"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal([]byte(result), &parsed))
|
||||
assert.Equal(t, []string{`C\+\+`}, parsed.Queries)
|
||||
})
|
||||
|
||||
t.Run("invalid escape: common regex shorthand", func(t *testing.T) {
|
||||
input := `{"queries": ["\d+\.\d+", "\bword\b", "foo\|bar"]}`
|
||||
result := RepairJSON(input)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
|
||||
var parsed struct {
|
||||
Queries []string `json:"queries"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal([]byte(result), &parsed))
|
||||
// \b is a VALID JSON escape (backspace) — the LLM gets what it asked
|
||||
// for (a backspace char), we don't silently rewrite valid escapes.
|
||||
// \d, \., \| are invalid → repaired to literal backslash sequences.
|
||||
assert.Equal(t, `\d+\.\d+`, parsed.Queries[0])
|
||||
assert.Equal(t, "\bword\b", parsed.Queries[1])
|
||||
assert.Equal(t, `foo\|bar`, parsed.Queries[2])
|
||||
})
|
||||
|
||||
t.Run("invalid escape repair is idempotent", func(t *testing.T) {
|
||||
input := `{"queries": ["C\+\+"]}`
|
||||
once := RepairJSON(input)
|
||||
twice := RepairJSON(once)
|
||||
assert.Equal(t, once, twice)
|
||||
})
|
||||
|
||||
t.Run("stray fragment after empty object", func(t *testing.T) {
|
||||
// Streaming providers sometimes append a lone `""` fragment after the
|
||||
// arguments are already complete.
|
||||
result := RepairJSON(`{}""`)
|
||||
assert.Equal(t, "{}", result)
|
||||
})
|
||||
|
||||
t.Run("stray fragment after complete object", func(t *testing.T) {
|
||||
result := RepairJSON(`{"path": "/workspace/output"}""`)
|
||||
assert.True(t, json.Valid([]byte(result)), "result: %s", result)
|
||||
|
||||
var parsed struct {
|
||||
Path string `json:"path"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal([]byte(result), &parsed))
|
||||
assert.Equal(t, "/workspace/output", parsed.Path)
|
||||
})
|
||||
|
||||
t.Run("duplicated payload keeps first value", func(t *testing.T) {
|
||||
result := RepairJSON(`{"a": 1}{"a": 1}`)
|
||||
assert.Equal(t, `{"a": 1}`, result)
|
||||
})
|
||||
|
||||
t.Run("brace inside string does not trigger trim", func(t *testing.T) {
|
||||
input := `{"query": "}", "limit": 1}`
|
||||
result := RepairJSON(input)
|
||||
assert.Equal(t, input, result)
|
||||
})
|
||||
|
||||
t.Run("valid escapes are preserved", func(t *testing.T) {
|
||||
// Valid JSON escapes must pass through untouched.
|
||||
input := `{"path": "C:\\Users\\x", "line": "a\nb", "quote": "say \"hi\""}`
|
||||
result := RepairJSON(input)
|
||||
assert.Equal(t, input, result)
|
||||
assert.True(t, json.Valid([]byte(result)))
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user