|
|
@ -3,89 +3,81 @@ package graphvent
|
|
|
|
import (
|
|
|
|
import (
|
|
|
|
"testing"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
"time"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"net"
|
|
|
|
|
|
|
|
"errors"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
"encoding/json"
|
|
|
|
|
|
|
|
"bytes"
|
|
|
|
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/ecdh"
|
|
|
|
"crypto/ecdh"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/tls"
|
|
|
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func TestGQLDBLoad(t * testing.T) {
|
|
|
|
func TestGQLDBLoad(t * testing.T) {
|
|
|
|
ctx := logTestContext(t, []string{"test"})
|
|
|
|
ctx := logTestContext(t, []string{"test", "db"})
|
|
|
|
l1 := NewListener(RandID(), "Test Listener 1")
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "L1_ID: %s", l1.ID().String())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
t1 := NewThread(RandID(), "Test Thread 1", "init", nil, BaseThreadActions, BaseThreadHandlers)
|
|
|
|
ListenerNodeType := NodeType("LISTENER")
|
|
|
|
ctx.Log.Logf("test", "T1_ID: %s", t1.ID().String())
|
|
|
|
err := ctx.RegisterNodeType(ListenerNodeType, []ExtType{ACLExtType, ListenerExtType, LockableExtType})
|
|
|
|
listen_id := RandID()
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "LISTENER_ID: %s", listen_id.String())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
u1_key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
u1 := NewUser("Test User", time.Now(), &u1_key.PublicKey, []byte{})
|
|
|
|
l1 := NewNode(RandID(), ListenerNodeType)
|
|
|
|
ctx.Log.Logf("test", "U1_ID: %s", u1.ID().String())
|
|
|
|
l1.Extensions[ACLExtType] = NewACLExt(nil)
|
|
|
|
|
|
|
|
listener_ext := NewListenerExt(10)
|
|
|
|
|
|
|
|
l1.Extensions[ListenerExtType] = listener_ext
|
|
|
|
|
|
|
|
l1.Extensions[LockableExtType] = NewLockableExt(nil, nil, nil, nil)
|
|
|
|
|
|
|
|
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
ctx.Log.Logf("test", "L1_ID: %s", l1.ID)
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
gql := NewGQLThread(RandID(), "GQL Thread", "init", ":0", ecdh.P256(), key, nil, nil)
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "GQL_ID: %s", gql.ID().String())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Policy to allow gql to perform all action on all resources
|
|
|
|
TestThreadNodeType := NodeType("TEST_THREAD")
|
|
|
|
p1 := NewPerNodePolicy(RandID(), map[NodeID]NodeActions{
|
|
|
|
err = ctx.RegisterNodeType(TestThreadNodeType, []ExtType{ACLExtType, ThreadExtType, LockableExtType})
|
|
|
|
gql.ID(): NewNodeActions(nil, []string{"*"}),
|
|
|
|
fatalErr(t, err)
|
|
|
|
})
|
|
|
|
|
|
|
|
p2 := NewSimplePolicy(RandID(), NewNodeActions(NodeActions{
|
|
|
|
|
|
|
|
"signal": []string{"status"},
|
|
|
|
|
|
|
|
}, nil))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
context := NewWriteContext(ctx)
|
|
|
|
t1 := NewNode(RandID(), TestThreadNodeType)
|
|
|
|
err = UpdateStates(context, &gql, LockMap{
|
|
|
|
t1.Extensions[ACLExtType] = NewACLExt(nil)
|
|
|
|
p1.ID(): LockInfo{&p1, nil},
|
|
|
|
t1.Extensions[ThreadExtType], err = NewThreadExt(ctx, BaseThreadType, nil, nil, "init", nil)
|
|
|
|
p2.ID(): LockInfo{&p2, nil},
|
|
|
|
|
|
|
|
}, func(context *StateContext) error {
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
t1.Extensions[LockableExtType] = NewLockableExt(nil, nil, nil, nil)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "T1_ID: %s", t1.ID)
|
|
|
|
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "P1_ID: %s", p1.ID().String())
|
|
|
|
TestUserNodeType := NodeType("TEST_USER")
|
|
|
|
ctx.Log.Logf("test", "P2_ID: %s", p2.ID().String())
|
|
|
|
err = ctx.RegisterNodeType(TestUserNodeType, []ExtType{ACLExtType})
|
|
|
|
err = AttachPolicies(ctx, &gql, &p1, &p2)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
err = AttachPolicies(ctx, &l1, &p1, &p2)
|
|
|
|
|
|
|
|
|
|
|
|
u1 := NewNode(RandID(), TestUserNodeType)
|
|
|
|
|
|
|
|
u1.Extensions[ACLExtType] = NewACLExt(nil)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "U1_ID: %s", u1.ID)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TestGQLNodeType := NodeType("TEST_GQL")
|
|
|
|
|
|
|
|
err = ctx.RegisterNodeType(TestGQLNodeType, []ExtType{ACLExtType, GroupExtType, GQLExtType, ThreadExtType, LockableExtType})
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
err = AttachPolicies(ctx, &t1, &p1, &p2)
|
|
|
|
|
|
|
|
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
err = AttachPolicies(ctx, &u1, &p1, &p2)
|
|
|
|
|
|
|
|
|
|
|
|
gql := NewNode(RandID(), TestGQLNodeType)
|
|
|
|
|
|
|
|
gql.Extensions[ACLExtType] = NewACLExt(nil)
|
|
|
|
|
|
|
|
gql.Extensions[GroupExtType] = NewGroupExt(nil)
|
|
|
|
|
|
|
|
gql.Extensions[GQLExtType] = NewGQLExt(":0", ecdh.P256(), key, nil, nil)
|
|
|
|
|
|
|
|
gql.Extensions[ThreadExtType], err = NewThreadExt(ctx, GQLThreadType, nil, nil, "ini", nil)
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
gql.Extensions[LockableExtType] = NewLockableExt(nil, nil, nil, nil)
|
|
|
|
|
|
|
|
|
|
|
|
info := NewParentThreadInfo(true, "start", "restore")
|
|
|
|
ctx.Log.Logf("test", "GQL_ID: %s", gql.ID)
|
|
|
|
context = NewWriteContext(ctx)
|
|
|
|
|
|
|
|
err = UpdateStates(context, &gql, NewLockMap(
|
|
|
|
|
|
|
|
NewLockInfo(&gql, []string{"users"}),
|
|
|
|
|
|
|
|
), func(context *StateContext) error {
|
|
|
|
|
|
|
|
gql.UserMap[u1.ID()] = &u1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
err := LinkThreads(context, &gql, &gql, ChildInfo{&t1, map[InfoType]interface{}{
|
|
|
|
info := ParentInfo{true, "start", "restore"}
|
|
|
|
"parent": &info,
|
|
|
|
context := NewWriteContext(ctx)
|
|
|
|
|
|
|
|
err = UpdateStates(context, &gql, NewACLInfo(&gql, []string{"users"}), func(context *StateContext) error {
|
|
|
|
|
|
|
|
err := LinkThreads(context, &gql, &gql, ChildInfo{&t1, map[InfoType]Info{
|
|
|
|
|
|
|
|
ParentInfoType: &info,
|
|
|
|
}})
|
|
|
|
}})
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return LinkLockables(context, &gql, &l1, []LockableNode{&gql})
|
|
|
|
return LinkLockables(context, &gql, &l1, []*Node{&gql})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
err = Signal(context, &gql, &gql, NewStatusSignal("child_linked", t1.ID()))
|
|
|
|
err = Signal(context, &gql, &gql, NewStatusSignal("child_linked", t1.ID))
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
err = Signal(context, &gql, &gql, AbortSignal)
|
|
|
|
err = Signal(context, &gql, &gql, AbortSignal)
|
|
|
@ -96,10 +88,10 @@ func TestGQLDBLoad(t * testing.T) {
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
(*GraphTester)(t).WaitForStatus(ctx, l1.Chan, "aborted", 100*time.Millisecond, "Didn't receive aborted on listener")
|
|
|
|
(*GraphTester)(t).WaitForStatus(ctx, listener_ext.Chan, "aborted", 100*time.Millisecond, "Didn't receive aborted on listener")
|
|
|
|
|
|
|
|
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
err = UseStates(context, &gql, LockList([]Node{&gql, &u1}, nil), func(context *StateContext) error {
|
|
|
|
err = UseStates(context, &gql, ACLList([]*Node{&gql, &u1}, nil), func(context *StateContext) error {
|
|
|
|
ser1, err := gql.Serialize()
|
|
|
|
ser1, err := gql.Serialize()
|
|
|
|
ser2, err := u1.Serialize()
|
|
|
|
ser2, err := u1.Serialize()
|
|
|
|
ctx.Log.Logf("test", "\n%s\n\n", ser1)
|
|
|
|
ctx.Log.Logf("test", "\n%s\n\n", ser1)
|
|
|
@ -107,150 +99,30 @@ func TestGQLDBLoad(t * testing.T) {
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
gql_loaded, err := LoadNode(ctx, gql.ID())
|
|
|
|
// Clear all loaded nodes from the context so it loads them from the database
|
|
|
|
|
|
|
|
ctx.Nodes = NodeMap{}
|
|
|
|
|
|
|
|
gql_loaded, err := LoadNode(ctx, gql.ID)
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
var l1_loaded *Listener = nil
|
|
|
|
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
context = NewReadContext(ctx)
|
|
|
|
err = UseStates(context, gql_loaded, NewLockInfo(gql_loaded, []string{"users", "children", "requirements"}), func(context *StateContext) error {
|
|
|
|
err = UseStates(context, gql_loaded, NewACLInfo(gql_loaded, []string{"users", "children", "requirements"}), func(context *StateContext) error {
|
|
|
|
ser, err := gql_loaded.Serialize()
|
|
|
|
ser, err := gql_loaded.Serialize()
|
|
|
|
|
|
|
|
lockable_ext, err := GetExt[*LockableExt](gql_loaded)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
return err
|
|
|
|
|
|
|
|
}
|
|
|
|
ctx.Log.Logf("test", "\n%s\n\n", ser)
|
|
|
|
ctx.Log.Logf("test", "\n%s\n\n", ser)
|
|
|
|
dependency := gql_loaded.(*GQLThread).Thread.Dependencies[l1.ID()].(*Listener)
|
|
|
|
dependency := lockable_ext.Dependencies[l1.ID]
|
|
|
|
l1_loaded = dependency
|
|
|
|
listener_ext, err = GetExt[*ListenerExt](dependency)
|
|
|
|
u_loaded := gql_loaded.(*GQLThread).UserMap[u1.ID()]
|
|
|
|
if err != nil {
|
|
|
|
err = UseStates(context, gql_loaded, NewLockInfo(u_loaded, nil), func(context *StateContext) error {
|
|
|
|
|
|
|
|
ser, err := u_loaded.Serialize()
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "\n%s\n\n", ser)
|
|
|
|
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Signal(context, gql_loaded, gql_loaded, StopSignal)
|
|
|
|
Signal(context, gql_loaded, gql_loaded, StopSignal)
|
|
|
|
return err
|
|
|
|
return err
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
err = ThreadLoop(ctx, gql_loaded.(ThreadNode), "start")
|
|
|
|
err = ThreadLoop(ctx, gql_loaded, "start")
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
(*GraphTester)(t).WaitForStatus(ctx, l1_loaded.Chan, "stopped", 100*time.Millisecond, "Didn't receive stopped on update_channel_2")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func TestGQLAuth(t * testing.T) {
|
|
|
|
|
|
|
|
ctx := logTestContext(t, []string{"test", "gql", "policy"})
|
|
|
|
|
|
|
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p2 := NewSimplePolicy(RandID(), NewNodeActions(NodeActions{
|
|
|
|
|
|
|
|
"signal": []string{"status"},
|
|
|
|
|
|
|
|
}, nil))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
l1 := NewListener(RandID(), "GQL Thread")
|
|
|
|
|
|
|
|
err = AttachPolicies(ctx, &l1, &p2)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p3 := NewPerNodePolicy(RandID(), map[NodeID]NodeActions{
|
|
|
|
|
|
|
|
l1.ID(): NewNodeActions(nil, []string{"*"}),
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gql := NewGQLThread(RandID(), "GQL Thread", "init", ":0", ecdh.P256(), key, nil, nil)
|
|
|
|
|
|
|
|
err = AttachPolicies(ctx, &gql, &p2, &p3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
context := NewWriteContext(ctx)
|
|
|
|
|
|
|
|
err = LinkLockables(context, &l1, &l1, []LockableNode{&gql})
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
done := make(chan error, 1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
go func(done chan error, thread ThreadNode) {
|
|
|
|
|
|
|
|
timeout := time.After(2*time.Second)
|
|
|
|
|
|
|
|
select {
|
|
|
|
|
|
|
|
case <-timeout:
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "TIMEOUT")
|
|
|
|
|
|
|
|
case <-done:
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "DONE")
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
context := NewReadContext(ctx)
|
|
|
|
|
|
|
|
err := Signal(context, thread, thread, StopSignal)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
fatalErr(t, err)
|
|
|
|
}(done, &gql)
|
|
|
|
(*GraphTester)(t).WaitForStatus(ctx, listener_ext.Chan, "stopped", 100*time.Millisecond, "Didn't receive stopped on update_channel_2")
|
|
|
|
|
|
|
|
|
|
|
|
go func(thread ThreadNode){
|
|
|
|
|
|
|
|
(*GraphTester)(t).WaitForStatus(ctx, l1.Chan, "server_started", 100*time.Millisecond, "Server didn't start")
|
|
|
|
|
|
|
|
port := gql.tcp_listener.Addr().(*net.TCPAddr).Port
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "GQL_PORT: %d", port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
customTransport := &http.Transport{
|
|
|
|
|
|
|
|
Proxy: http.DefaultTransport.(*http.Transport).Proxy,
|
|
|
|
|
|
|
|
DialContext: http.DefaultTransport.(*http.Transport).DialContext,
|
|
|
|
|
|
|
|
MaxIdleConns: http.DefaultTransport.(*http.Transport).MaxIdleConns,
|
|
|
|
|
|
|
|
IdleConnTimeout: http.DefaultTransport.(*http.Transport).IdleConnTimeout,
|
|
|
|
|
|
|
|
ExpectContinueTimeout: http.DefaultTransport.(*http.Transport).ExpectContinueTimeout,
|
|
|
|
|
|
|
|
TLSHandshakeTimeout: http.DefaultTransport.(*http.Transport).TLSHandshakeTimeout,
|
|
|
|
|
|
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
client := &http.Client{Transport: customTransport}
|
|
|
|
|
|
|
|
url := fmt.Sprintf("https://localhost:%d/auth", port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
id, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
auth_req, ec_key, err := NewAuthReqJSON(ecdh.P256(), id)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
str, err := json.Marshal(auth_req)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
b := bytes.NewBuffer(str)
|
|
|
|
|
|
|
|
req, err := http.NewRequest("PUT", url, b)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resp, err := client.Do(req)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var j AuthRespJSON
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &j)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shared, err := ParseAuthRespJSON(j, elliptic.P256(), ecdh.P256(), ec_key)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
url = fmt.Sprintf("https://localhost:%d/gql", port)
|
|
|
|
|
|
|
|
ser, err := json.MarshalIndent(&GQLPayload{
|
|
|
|
|
|
|
|
Query: "query { Self { Users { ID, Name } } }",
|
|
|
|
|
|
|
|
}, "", " ")
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
b = bytes.NewBuffer(ser)
|
|
|
|
|
|
|
|
req, err = http.NewRequest("GET", url, b)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req.SetBasicAuth(KeyID(&id.PublicKey).String(), base64.StdEncoding.EncodeToString(shared))
|
|
|
|
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
body, err = io.ReadAll(resp.Body)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "TEST_RESP: %s", body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
req.SetBasicAuth(KeyID(&id.PublicKey).String(), "BAD_PASSWORD")
|
|
|
|
|
|
|
|
resp, err = client.Do(req)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
body, err = io.ReadAll(resp.Body)
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
resp.Body.Close()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ctx.Log.Logf("test", "TEST_RESP: %s", body)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
done <- nil
|
|
|
|
|
|
|
|
}(&gql)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
err = ThreadLoop(ctx, &gql, "start")
|
|
|
|
|
|
|
|
fatalErr(t, err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|