Skip to content

Commit 215ed89

Browse files
authored
Merge pull request #187 from julwrites/staging
2 parents 44b5001 + 017f6ab commit 215ed89

11 files changed

Lines changed: 239 additions & 47 deletions

pkg/app/api_client.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"io"
88
"log"
99
"net/http"
10+
"net/url"
1011
"sync"
1112

1213
"github.com/julwrites/ScriptureBot/pkg/secrets"
@@ -87,6 +88,16 @@ var SubmitQuery = func(req QueryRequest, result interface{}) error {
8788
{Verse: "John 3:16", URL: "https://example.com/John3:16"},
8889
},
8990
}
91+
case *PromptResponse:
92+
*r = PromptResponse{
93+
Data: OQueryResponse{
94+
Text: "This is a mock response.",
95+
References: []SearchResult{
96+
{Verse: "John 3:16", URL: "https://example.com/John3:16"},
97+
},
98+
},
99+
Meta: Meta{AIProvider: "mock"},
100+
}
90101
case *VerseResponse:
91102
*r = VerseResponse{
92103
Verse: "For God so loved the world...",
@@ -137,3 +148,84 @@ var SubmitQuery = func(req QueryRequest, result interface{}) error {
137148

138149
return nil
139150
}
151+
152+
// GetVersions retrieves the list of available Bible versions.
153+
var GetVersions = func(page, limit int, name, language, sort string) (*VersionsResponse, error) {
154+
apiURL, apiKey := getAPIConfig()
155+
if apiURL == "" {
156+
return nil, fmt.Errorf("BIBLE_API_URL environment variable is not set")
157+
}
158+
159+
// Mock response for tests
160+
if apiURL == "https://example.com" {
161+
return &VersionsResponse{
162+
Data: []Version{
163+
{Code: "ESV", Name: "English Standard Version", Language: "English"},
164+
{Code: "NIV", Name: "New International Version", Language: "English"},
165+
},
166+
Total: 2,
167+
Page: 1,
168+
Limit: 20,
169+
}, nil
170+
}
171+
172+
client := &http.Client{}
173+
174+
reqURL, err := url.Parse(apiURL + "/bible-versions")
175+
if err != nil {
176+
return nil, fmt.Errorf("failed to parse url: %v", err)
177+
}
178+
q := reqURL.Query()
179+
if page > 0 {
180+
q.Set("page", fmt.Sprintf("%d", page))
181+
}
182+
if limit > 0 {
183+
q.Set("limit", fmt.Sprintf("%d", limit))
184+
}
185+
if name != "" {
186+
q.Set("name", name)
187+
}
188+
if language != "" {
189+
q.Set("language", language)
190+
}
191+
if sort != "" {
192+
q.Set("sort", sort)
193+
}
194+
reqURL.RawQuery = q.Encode()
195+
196+
httpReq, err := http.NewRequest("GET", reqURL.String(), nil)
197+
if err != nil {
198+
return nil, fmt.Errorf("failed to create request: %v", err)
199+
}
200+
201+
httpReq.Header.Set("Content-Type", "application/json")
202+
if apiKey != "" {
203+
httpReq.Header.Set("X-API-KEY", apiKey)
204+
}
205+
206+
resp, err := client.Do(httpReq)
207+
if err != nil {
208+
return nil, fmt.Errorf("request failed: %v", err)
209+
}
210+
defer resp.Body.Close()
211+
212+
body, err := io.ReadAll(resp.Body)
213+
if err != nil {
214+
return nil, fmt.Errorf("failed to read response body: %v", err)
215+
}
216+
217+
if resp.StatusCode != http.StatusOK {
218+
var errResp ErrorResponse
219+
if jsonErr := json.Unmarshal(body, &errResp); jsonErr == nil && errResp.Error.Message != "" {
220+
return nil, fmt.Errorf("api error (%d): %s", errResp.Error.Code, errResp.Error.Message)
221+
}
222+
return nil, fmt.Errorf("api request failed with status %d: %s", resp.StatusCode, string(body))
223+
}
224+
225+
var result VersionsResponse
226+
if err := json.Unmarshal(body, &result); err != nil {
227+
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
228+
}
229+
230+
return &result, nil
231+
}

pkg/app/api_client_test.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestSubmitQuery(t *testing.T) {
2828
// Avoid using Prompt ("hello") as it triggers the LLM which might be unstable (500 errors).
2929
req := QueryRequest{
3030
Query: QueryObject{Verses: []string{"John 3:16"}},
31-
Context: QueryContext{User: UserContext{Version: "NIV"}},
31+
User: UserOptions{Version: "NIV"},
3232
}
3333
var resp VerseResponse
3434
err := SubmitQuery(req, &resp)
@@ -53,3 +53,32 @@ func TestSubmitQuery(t *testing.T) {
5353
}
5454
})
5555
}
56+
57+
func TestGetVersions(t *testing.T) {
58+
t.Run("Success", func(t *testing.T) {
59+
defer SetEnv("BIBLE_API_URL", "https://example.com")()
60+
defer SetEnv("BIBLE_API_KEY", "api_key")()
61+
ResetAPIConfigCache()
62+
63+
resp, err := GetVersions(1, 10, "", "", "")
64+
if err != nil {
65+
t.Errorf("Unexpected error: %v", err)
66+
}
67+
if resp.Total != 2 {
68+
t.Errorf("Expected total 2, got %d", resp.Total)
69+
}
70+
if len(resp.Data) != 2 {
71+
t.Errorf("Expected 2 versions, got %d", len(resp.Data))
72+
}
73+
})
74+
75+
t.Run("No URL", func(t *testing.T) {
76+
defer SetEnv("BIBLE_API_URL", "")()
77+
ResetAPIConfigCache()
78+
79+
_, err := GetVersions(1, 10, "", "", "")
80+
if err == nil {
81+
t.Error("Expected error when BIBLE_API_URL is unset")
82+
}
83+
})
84+
}

pkg/app/api_models.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,33 @@ package app
22

33
// Request Models
44

5-
type UserContext struct {
6-
Version string `json:"version,omitempty"`
5+
type UserOptions struct {
6+
Version string `json:"version,omitempty"`
7+
AIProvider string `json:"ai_provider,omitempty"`
8+
}
9+
10+
type Options struct {
11+
Stream bool `json:"stream,omitempty"`
712
}
813

914
type QueryContext struct {
10-
History []string `json:"history,omitempty"`
11-
Schema string `json:"schema,omitempty"`
12-
Verses []string `json:"verses,omitempty"`
13-
Words []string `json:"words,omitempty"`
14-
User UserContext `json:"user,omitempty"`
15+
History []string `json:"history,omitempty"`
16+
Schema string `json:"schema,omitempty"`
17+
Verses []string `json:"verses,omitempty"`
18+
Words []string `json:"words,omitempty"`
1519
}
1620

1721
type QueryObject struct {
18-
Verses []string `json:"verses,omitempty"`
19-
Words []string `json:"words,omitempty"`
20-
Prompt string `json:"prompt,omitempty"`
22+
Verses []string `json:"verses,omitempty"`
23+
Words []string `json:"words,omitempty"`
24+
Prompt string `json:"prompt,omitempty"`
25+
Context QueryContext `json:"context,omitempty"`
2126
}
2227

2328
type QueryRequest struct {
24-
Query QueryObject `json:"query"`
25-
Context QueryContext `json:"context,omitempty"`
29+
Query QueryObject `json:"query"`
30+
User UserOptions `json:"user,omitempty"`
31+
Options Options `json:"options,omitempty"`
2632
}
2733

2834
// Response Models
@@ -44,6 +50,15 @@ type OQueryResponse struct {
4450
References []SearchResult `json:"references"`
4551
}
4652

53+
type Meta struct {
54+
AIProvider string `json:"ai_provider"`
55+
}
56+
57+
type PromptResponse struct {
58+
Data OQueryResponse `json:"data"`
59+
Meta Meta `json:"meta"`
60+
}
61+
4762
type ErrorResponse struct {
4863
Error ErrorDetails `json:"error"`
4964
}
@@ -52,3 +67,17 @@ type ErrorDetails struct {
5267
Code int `json:"code"`
5368
Message string `json:"message"`
5469
}
70+
71+
type Version struct {
72+
Name string `json:"name"`
73+
Code string `json:"code"`
74+
Language string `json:"language"`
75+
Providers map[string]string `json:"providers"`
76+
}
77+
78+
type VersionsResponse struct {
79+
Data []Version `json:"data"`
80+
Total int `json:"total"`
81+
Page int `json:"page"`
82+
Limit int `json:"limit"`
83+
}

pkg/app/api_models_test.go

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,16 @@ func TestQueryRequest_Marshal(t *testing.T) {
1212
Verses: []string{"John 3:16"},
1313
Words: []string{"Love"},
1414
Prompt: "Tell me about love",
15-
},
16-
Context: QueryContext{
17-
History: []string{"Previous query"},
18-
Schema: "custom",
19-
Verses: []string{"Gen 1:1"},
20-
Words: []string{"Create"},
21-
User: UserContext{
22-
Version: "NIV",
15+
Context: QueryContext{
16+
History: []string{"Previous query"},
17+
Schema: "custom",
18+
Verses: []string{"Gen 1:1"},
19+
Words: []string{"Create"},
2320
},
2421
},
22+
User: UserOptions{
23+
Version: "NIV",
24+
},
2525
}
2626

2727
data, err := json.Marshal(req)
@@ -78,7 +78,35 @@ func TestWordSearchResponse_Unmarshal(t *testing.T) {
7878
}
7979
}
8080

81+
func TestPromptResponse_Unmarshal(t *testing.T) {
82+
jsonStr := `{
83+
"data": {
84+
"text": "God is love.",
85+
"references": [{"verse": "1 John 4:8", "url": "http://bible.com"}]
86+
},
87+
"meta": {
88+
"ai_provider": "openai"
89+
}
90+
}`
91+
var resp PromptResponse
92+
err := json.Unmarshal([]byte(jsonStr), &resp)
93+
if err != nil {
94+
t.Fatalf("Failed to unmarshal PromptResponse: %v", err)
95+
}
96+
97+
if resp.Data.Text != "God is love." {
98+
t.Errorf("Expected text 'God is love.', got '%s'", resp.Data.Text)
99+
}
100+
if len(resp.Data.References) != 1 {
101+
t.Errorf("Expected 1 reference, got %d", len(resp.Data.References))
102+
}
103+
if resp.Meta.AIProvider != "openai" {
104+
t.Errorf("Expected AI provider 'openai', got '%s'", resp.Meta.AIProvider)
105+
}
106+
}
107+
81108
func TestOQueryResponse_Unmarshal(t *testing.T) {
109+
// This tests direct unmarshal of OQueryResponse, which is still used internally or if schema matches
82110
jsonStr := `{
83111
"text": "God is love.",
84112
"references": [{"verse": "1 John 4:8", "url": "http://bible.com"}]

pkg/app/ask.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,16 @@ func GetBibleAskWithContext(env def.SessionData, contextVerses []string) def.Ses
3333
req := QueryRequest{
3434
Query: QueryObject{
3535
Prompt: env.Msg.Message,
36-
},
37-
Context: QueryContext{
38-
User: UserContext{
39-
Version: config.Version,
36+
Context: QueryContext{
37+
Verses: contextVerses,
4038
},
41-
Verses: contextVerses,
39+
},
40+
User: UserOptions{
41+
Version: config.Version,
4242
},
4343
}
4444

45-
var resp OQueryResponse
45+
var resp PromptResponse
4646
err := SubmitQuery(req, &resp)
4747
if err != nil {
4848
log.Printf("Error asking bible: %v", err)
@@ -51,11 +51,11 @@ func GetBibleAskWithContext(env def.SessionData, contextVerses []string) def.Ses
5151
}
5252

5353
var sb strings.Builder
54-
sb.WriteString(ParseToTelegramHTML(resp.Text))
54+
sb.WriteString(ParseToTelegramHTML(resp.Data.Text))
5555

56-
if len(resp.References) > 0 {
56+
if len(resp.Data.References) > 0 {
5757
sb.WriteString("\n\n<b>References:</b>")
58-
for _, ref := range resp.References {
58+
for _, ref := range resp.Data.References {
5959
sb.WriteString(fmt.Sprintf("\n• %s", stdhtml.EscapeString(ref.Verse)))
6060
}
6161
}

pkg/app/ask_test.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ func TestGetBibleAsk(t *testing.T) {
6666
if capturedReq.Query.Prompt != "Explain this" {
6767
t.Errorf("Expected Query.Prompt to be 'Explain this', got '%s'", capturedReq.Query.Prompt)
6868
}
69-
if len(capturedReq.Context.Verses) != 2 {
70-
t.Errorf("Expected Context.Verses to have 2 items, got %v", capturedReq.Context.Verses)
69+
if len(capturedReq.Query.Context.Verses) != 2 {
70+
t.Errorf("Expected Context.Verses to have 2 items, got %v", capturedReq.Query.Context.Verses)
7171
}
72-
if capturedReq.Context.Verses[0] != "John 3:16" {
73-
t.Errorf("Expected Context.Verses[0] to be 'John 3:16', got '%s'", capturedReq.Context.Verses[0])
72+
if capturedReq.Query.Context.Verses[0] != "John 3:16" {
73+
t.Errorf("Expected Context.Verses[0] to be 'John 3:16', got '%s'", capturedReq.Query.Context.Verses[0])
7474
}
7575
})
7676

@@ -123,11 +123,13 @@ func TestGetBibleAsk(t *testing.T) {
123123

124124
// Mock SubmitQuery to return HTML
125125
SubmitQuery = func(req QueryRequest, result interface{}) error {
126-
if r, ok := result.(*OQueryResponse); ok {
127-
*r = OQueryResponse{
128-
Text: "<p>God is <b>Love</b></p>",
129-
References: []SearchResult{
130-
{Verse: "1 John 4:8"},
126+
if r, ok := result.(*PromptResponse); ok {
127+
*r = PromptResponse{
128+
Data: OQueryResponse{
129+
Text: "<p>God is <b>Love</b></p>",
130+
References: []SearchResult{
131+
{Verse: "1 John 4:8"},
132+
},
131133
},
132134
}
133135
}

pkg/app/passage.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,8 @@ func GetBiblePassage(env def.SessionData) def.SessionData {
240240
Query: QueryObject{
241241
Verses: []string{env.Msg.Message},
242242
},
243-
Context: QueryContext{
244-
User: UserContext{
245-
Version: config.Version,
246-
},
243+
User: UserOptions{
244+
Version: config.Version,
247245
},
248246
}
249247

pkg/app/passage_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func TestGetBiblePassage(t *testing.T) {
9393
if capturedReq.Query.Prompt != "" {
9494
t.Errorf("Expected Query.Prompt to be empty, got '%s'", capturedReq.Query.Prompt)
9595
}
96+
if capturedReq.User.Version != "NIV" {
97+
t.Errorf("Expected User.Version to be 'NIV', got '%s'", capturedReq.User.Version)
98+
}
9699
})
97100

98101
t.Run("Success: Response", func(t *testing.T) {

0 commit comments

Comments
 (0)