Skip to content

Commit 510d5cf

Browse files
committed
1 parent f1e14c4 commit 510d5cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+4801
-2
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
name: Publish go runtime version
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths:
8+
- 'go/**' # run only if something under go/ changed
9+
10+
permissions:
11+
contents: write # needed to push tags with GITHUB_TOKEN
12+
13+
jobs:
14+
create_tag:
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Checkout repository
19+
uses: actions/checkout@v3
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Set up Git for tagging
24+
run: |
25+
git config user.name "${{ github.actor }}"
26+
git config user.email "${{ github.actor }}@users.noreply.github.com"
27+
28+
- name: Get latest go/ tag and increment minor
29+
id: get_tag
30+
shell: bash
31+
run: |
32+
set -euo pipefail
33+
34+
# Ensure all tags are available
35+
git fetch --tags --force
36+
37+
prefix="go/v"
38+
39+
# Find the newest tag that starts with go/v*
40+
latest_tag="$(git tag -l "${prefix}*" --sort=-v:refname | head -n1 || true)"
41+
if [[ -z "${latest_tag}" ]]; then
42+
latest_tag="${prefix}0.0.0"
43+
fi
44+
echo "Latest tag: ${latest_tag}"
45+
46+
# Strip the prefix "go/v" and parse semver
47+
version="${latest_tag#${prefix}}"
48+
IFS='.' read -r major minor patch <<< "${version}"
49+
50+
# Bump minor, reset patch
51+
new_minor=$((minor + 1))
52+
new_version="${prefix}${major}.${new_minor}.0"
53+
54+
echo "Next tag: ${new_version}"
55+
echo "tag=${new_version}" >> "$GITHUB_OUTPUT"
56+
57+
- name: Create and push new tag
58+
run: |
59+
new_tag="${{ steps.get_tag.outputs.tag }}"
60+
git tag "${new_tag}"
61+
git push origin "${new_tag}"

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

go/agent.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package runtime
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/cloudimpl/polycode-runtime/go/sdk"
7+
)
8+
9+
type AgentBuilder struct {
10+
ctx context.Context
11+
sessionId string
12+
envId string
13+
agent string
14+
serviceClient ServiceClient
15+
}
16+
17+
func (r *AgentBuilder) WithEnvId(envId string) sdk.AgentBuilder {
18+
r.envId = envId
19+
return r
20+
}
21+
22+
func (r *AgentBuilder) Get() sdk.Agent {
23+
return &Agent{
24+
ctx: r.ctx,
25+
sessionId: r.sessionId,
26+
envId: r.envId,
27+
agent: r.agent,
28+
serviceClient: r.serviceClient,
29+
}
30+
}
31+
32+
var _ sdk.AgentBuilder = (*AgentBuilder)(nil)
33+
34+
type Agent struct {
35+
ctx context.Context
36+
sessionId string
37+
envId string
38+
agent string
39+
serviceClient ServiceClient
40+
}
41+
42+
func (r *Agent) Call(options sdk.TaskOptions, input sdk.AgentInput) (sdk.Response, error) {
43+
req := ExecAgentRequest{
44+
EnvId: r.envId,
45+
AgentName: r.agent,
46+
Options: options,
47+
Input: input,
48+
}
49+
50+
output, err := r.serviceClient.CallAgent(r.sessionId, req)
51+
if err != nil {
52+
fmt.Printf("client: exec task error: %v\n", err)
53+
return nil, err
54+
}
55+
56+
fmt.Printf("client: exec task output: %v\n", output)
57+
return &Response{
58+
output: output.Output,
59+
isError: output.IsError,
60+
error: output.Error,
61+
}, nil
62+
}
63+
64+
var _ sdk.Agent = (*Agent)(nil)

go/agent_test.go

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
package runtime
2+
3+
//import (
4+
// "context"
5+
// "reflect"
6+
// "testing"
7+
//
8+
// "github.com/cloudimpl/polycode-runtime-go/sdk"
9+
//)
10+
//
11+
//// ---- Mock client that satisfies the full ServiceClient interface ----
12+
//
13+
//type mockServiceClient struct {
14+
// t *testing.T
15+
// // Expectations used by ExecService assertions:
16+
// wantSessionId string
17+
// wantReq ExecServiceRequest
18+
//
19+
// // Return values for ExecService
20+
// out ExecServiceResponse
21+
// err error
22+
//
23+
// called bool
24+
//}
25+
//
26+
//func (m *mockServiceClient) ExecService(sessionId string, req ExecServiceRequest) (ExecServiceResponse, error) {
27+
// m.called = true
28+
//
29+
// if sessionId != m.wantSessionId {
30+
// m.t.Fatalf("ExecService: sessionId mismatch: got %q want %q", sessionId, m.wantSessionId)
31+
// }
32+
// if req.EnvId != m.wantReq.EnvId {
33+
// m.t.Fatalf("ExecService: EnvId mismatch: got %q want %q", req.EnvId, m.wantReq.EnvId)
34+
// }
35+
// if req.Service != m.wantReq.Service {
36+
// m.t.Fatalf("ExecService: Service mismatch: got %q want %q", req.Service, m.wantReq.Service)
37+
// }
38+
// if req.TenantId != m.wantReq.TenantId {
39+
// m.t.Fatalf("ExecService: TenantId mismatch: got %q want %q", req.TenantId, m.wantReq.TenantId)
40+
// }
41+
// if req.Method != m.wantReq.Method {
42+
// m.t.Fatalf("ExecService: Method mismatch: got %q want %q", req.Method, m.wantReq.Method)
43+
// }
44+
// if req.PartitionKey != m.wantReq.PartitionKey {
45+
// m.t.Fatalf("ExecService: PartitionKey mismatch: got %q want %q", req.PartitionKey, m.wantReq.PartitionKey)
46+
// }
47+
// if got, want := req.Headers[AgentNameHeader], m.wantReq.Headers[AgentNameHeader]; got != want {
48+
// m.t.Fatalf("ExecService: Headers[%s] mismatch: got %q want %q", AgentNameHeader, got, want)
49+
// }
50+
// if !reflect.DeepEqual(req.Options, m.wantReq.Options) {
51+
// m.t.Fatalf("ExecService: Options mismatch: got %#v want %#v", req.Options, m.wantReq.Options)
52+
// }
53+
// if !reflect.DeepEqual(req.Input, m.wantReq.Input) {
54+
// m.t.Fatalf("ExecService: Input mismatch: got %#v want %#v", req.Input, m.wantReq.Input)
55+
// }
56+
// return m.out, m.err
57+
//}
58+
//
59+
//// --- Unused interface methods: return zero values so the type compiles ---
60+
//
61+
//func (*mockServiceClient) StartApp(req StartAppRequest) error { return nil }
62+
//func (*mockServiceClient) ExecApp(string, ExecAppRequest) (ExecAppResponse, error) {
63+
// return ExecAppResponse{}, nil
64+
//}
65+
//func (*mockServiceClient) ExecApi(string, ExecApiRequest) (ExecApiResponse, error) {
66+
// return ExecApiResponse{}, nil
67+
//}
68+
//func (*mockServiceClient) ExecFunc(string, ExecFuncRequest) (ExecFuncResponse, error) {
69+
// return ExecFuncResponse{}, nil
70+
//}
71+
//func (*mockServiceClient) ExecFuncResult(string, ExecFuncResult) error { return nil }
72+
//func (*mockServiceClient) GetItem(string, QueryRequest) (map[string]interface{}, error) {
73+
// return nil, nil
74+
//}
75+
//func (*mockServiceClient) UnsafeGetItem(string, UnsafeQueryRequest) (map[string]interface{}, error) {
76+
// return nil, nil
77+
//}
78+
//func (*mockServiceClient) QueryItems(string, QueryRequest) ([]map[string]interface{}, error) {
79+
// return nil, nil
80+
//}
81+
//func (*mockServiceClient) UnsafeQueryItems(string, UnsafeQueryRequest) ([]map[string]interface{}, error) {
82+
// return nil, nil
83+
//}
84+
//func (*mockServiceClient) PutItem(string, PutRequest) error { return nil }
85+
//func (*mockServiceClient) UnsafePutItem(string, UnsafePutRequest) error { return nil }
86+
//func (*mockServiceClient) GetFile(string, GetFileRequest) (GetFileResponse, error) {
87+
// return GetFileResponse{}, nil
88+
//}
89+
//func (*mockServiceClient) GetFileDownloadLink(string, GetFileRequest) (GetLinkResponse, error) {
90+
// return GetLinkResponse{}, nil
91+
//}
92+
//func (*mockServiceClient) PutFile(string, PutFileRequest) error { return nil }
93+
//func (*mockServiceClient) GetFileUploadLink(string, GetUploadLinkRequest) (GetLinkResponse, error) {
94+
// return GetLinkResponse{}, nil
95+
//}
96+
//func (*mockServiceClient) DeleteFile(string, DeleteFileRequest) error { return nil }
97+
//func (*mockServiceClient) RenameFile(string, RenameFileRequest) error { return nil }
98+
//func (*mockServiceClient) ListFile(string, ListFilePageRequest) (ListFilePageResponse, error) {
99+
// return ListFilePageResponse{}, nil
100+
//}
101+
//func (*mockServiceClient) CreateFolder(string, CreateFolderRequest) error { return nil }
102+
//func (*mockServiceClient) EmitSignal(string, SignalEmitRequest) error { return nil }
103+
//func (*mockServiceClient) WaitForSignal(string, SignalWaitRequest) (SignalWaitResponse, error) {
104+
// return SignalWaitResponse{}, nil
105+
//}
106+
//func (*mockServiceClient) EmitRealtimeEvent(string, RealtimeEventEmitRequest) error { return nil }
107+
//func (*mockServiceClient) AcquireLock(string, AcquireLockRequest) error { return nil }
108+
//func (*mockServiceClient) ReleaseLock(string, ReleaseLockRequest) error { return nil }
109+
//func (*mockServiceClient) IncrementCounter(string, IncrementCounterRequest) (IncrementCounterResponse, error) {
110+
// return IncrementCounterResponse{}, nil
111+
//}
112+
//func (*mockServiceClient) GetMeta(string, GetMetaDataRequest) (map[string]interface{}, error) {
113+
// return nil, nil
114+
//}
115+
//func (*mockServiceClient) Acknowledge(string) error { return nil }
116+
//
117+
//// ---- Tests ----
118+
//
119+
//func TestAgentBuilder_WithTenantIdAndGet(t *testing.T) {
120+
// ctx := context.Background()
121+
// mock := &mockServiceClient{t: t}
122+
//
123+
// b := AgentBuilder{
124+
// ctx: ctx,
125+
// sessionId: "sess-123",
126+
// envId: "env-1",
127+
// agent: "echo",
128+
// serviceClient: mock,
129+
// }
130+
//
131+
// aIface := b.WithTenantId("tenant-xyz").Get()
132+
// a, ok := aIface.(Agent)
133+
// if !ok {
134+
// t.Fatalf("Get() did not return concrete Agent type")
135+
// }
136+
//
137+
// if a.tenantId != "tenant-xyz" || a.envId != "env-1" || a.agent != "echo" || a.sessionId != "sess-123" || a.ctx != ctx {
138+
// t.Fatalf("Agent fields not propagated correctly: %+v", a)
139+
// }
140+
//}
141+
//
142+
//func TestAgentCall_Success(t *testing.T) {
143+
// ctx := context.Background()
144+
// agentName := "echo"
145+
// input := sdk.AgentInput{SessionKey: "user-session-42"}
146+
// var opts sdk.TaskOptions
147+
//
148+
// wantReq := ExecServiceRequest{
149+
// EnvId: "env-1",
150+
// Service: "agent-service",
151+
// TenantId: "tenant-xyz",
152+
// PartitionKey: agentName + ":" + input.SessionKey,
153+
// Method: "CallAgent",
154+
// Options: opts,
155+
// Headers: map[string]string{AgentNameHeader: agentName},
156+
// Input: input,
157+
// }
158+
//
159+
// mock := &mockServiceClient{
160+
// t: t,
161+
// wantSessionId: "sess-123",
162+
// wantReq: wantReq,
163+
// out: ExecServiceResponse{
164+
// IsError: false,
165+
// Output: map[string]any{"ok": true, "msg": "pong"},
166+
// // Error zero-value
167+
// },
168+
// err: nil,
169+
// }
170+
//
171+
// a := Agent{
172+
// ctx: ctx,
173+
// sessionId: "sess-123",
174+
// envId: "env-1",
175+
// agent: agentName,
176+
// serviceClient: mock,
177+
// tenantId: "tenant-xyz",
178+
// }
179+
//
180+
// respIface := a.Call(opts, input)
181+
// resp, ok := respIface.(Response)
182+
// if !ok {
183+
// t.Fatalf("Call() did not return concrete Response type")
184+
// }
185+
//
186+
// if !mock.called {
187+
// t.Fatalf("ExecService was not called")
188+
// }
189+
// if resp.isError {
190+
// t.Fatalf("expected isError=false, got true (err=%+v)", resp.error)
191+
// }
192+
// if !reflect.DeepEqual(resp.output, mock.out.Output) {
193+
// t.Fatalf("output mismatch: got %#v want %#v", resp.output, mock.out.Output)
194+
// }
195+
//}
196+
//
197+
//func TestAgentCall_ExecError(t *testing.T) {
198+
// ctx := context.Background()
199+
// agentName := "alpha"
200+
// input := sdk.AgentInput{SessionKey: "s-1"}
201+
// var opts sdk.TaskOptions
202+
//
203+
// wantReq := ExecServiceRequest{
204+
// EnvId: "env-A",
205+
// Service: "agent-service",
206+
// TenantId: "tenant-A",
207+
// PartitionKey: agentName + ":" + input.SessionKey,
208+
// Method: "CallAgent",
209+
// Options: opts,
210+
// Headers: map[string]string{AgentNameHeader: agentName},
211+
// Input: input,
212+
// }
213+
//
214+
// mock := &mockServiceClient{
215+
// t: t,
216+
// wantSessionId: "sess-A",
217+
// wantReq: wantReq,
218+
// out: ExecServiceResponse{}, // ignored on error
219+
// err: assertableError{"boom-123"}, // triggers error path
220+
// }
221+
//
222+
// a := Agent{
223+
// ctx: ctx,
224+
// sessionId: "sess-A",
225+
// envId: "env-A",
226+
// agent: agentName,
227+
// serviceClient: mock,
228+
// tenantId: "tenant-A",
229+
// }
230+
//
231+
// respIface := a.Call(opts, input)
232+
// resp, ok := respIface.(Response)
233+
// if !ok {
234+
// t.Fatalf("Call() did not return concrete Response type")
235+
// }
236+
//
237+
// if !mock.called {
238+
// t.Fatalf("ExecService was not called")
239+
// }
240+
// if !resp.isError {
241+
// t.Fatalf("expected isError=true, got false")
242+
// }
243+
//}
244+
//
245+
//// Tiny error type for the error-path test
246+
//type assertableError struct{ msg string }
247+
//
248+
//func (e assertableError) Error() string { return e.msg }

0 commit comments

Comments
 (0)