Skip to content

Instantly share code, notes, and snippets.

@adoublef
Last active February 1, 2024 12:04
Show Gist options
  • Save adoublef/6ebfbd0bd6acfff2702bddca0e67de0b to your computer and use it in GitHub Desktop.
Save adoublef/6ebfbd0bd6acfff2702bddca0e67de0b to your computer and use it in GitHub Desktop.
Keto Testcontainers
package keto
import (
"context"
"fmt"
"net"
"github.com/testcontainers/testcontainers-go"
)
type KetoContainer struct {
testcontainers.Container
}
func (c *KetoContainer) WriteAPIAddress(ctx context.Context) (string, error) {
containerPort, err := c.MappedPort(ctx, "4467/tcp")
if err != nil {
return "", err
}
host, err := c.Host(ctx)
if err != nil {
return "", err
}
connStr := fmt.Sprintf("http://%s", net.JoinHostPort(host, containerPort.Port()))
return connStr, nil
}
func (c *KetoContainer) ReadAPIAddress(ctx context.Context) (string, error) {
containerPort, err := c.MappedPort(ctx, "4466/tcp")
if err != nil {
return "", err
}
host, err := c.Host(ctx)
if err != nil {
return "", err
}
connStr := fmt.Sprintf("http://%s", net.JoinHostPort(host, containerPort.Port()))
return connStr, nil
}
func RunContainer(ctx context.Context, opts ...testcontainers.ContainerCustomizer) (*KetoContainer, error) {
req := testcontainers.ContainerRequest{
Image: "oryd/keto:v0.11.1",
ExposedPorts: []string{"4466/tcp", "4467/tcp"},
Cmd: []string{"serve"},
Files: []testcontainers.ContainerFile{},
}
genericContainerReq := testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
}
for _, opt := range opts {
opt.Customize(&genericContainerReq)
}
container, err := testcontainers.GenericContainer(ctx, genericContainerReq)
if err != nil {
return nil, err
}
return &KetoContainer{Container: container}, nil
}
func WithVolumeBind(host, target string, mode int64) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) {
f := testcontainers.ContainerFile{
HostFilePath: host,
ContainerFilePath: target,
FileMode: mode,
}
req.Files = append(req.Files, f)
}
}
func WithCommand(cmds ...string) testcontainers.CustomizeRequestOption {
return func(req *testcontainers.GenericContainerRequest) {
req.Cmd = append(req.Cmd, cmds...)
}
}
package keto
import (
"context"
"testing"
ory "github.com/ory/client-go"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
func TestKeto(t *testing.T) {
ctx := context.Background()
container, err := RunContainer(ctx,
testcontainers.WithWaitStrategy(wait.ForHTTP("/health/ready").WithPort("4467")),
WithVolumeBind("./testdata/namespaces.ts", "/tmp/namespaces.ts", 0777),
WithVolumeBind("./testdata/keto.yml", "/tmp/keto.yml", 0777),
WithCommand("-c", "/tmp/keto.yml"),
)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := container.Terminate(ctx); err != nil {
t.Fatalf("failed to terminate container: %s", err)
}
})
// perform assertions
var namespace = "Blog"
var object = "secret_post"
var relation = "view"
var subjectId = "Bob"
payload := ory.CreateRelationshipBody{
Namespace: &namespace,
Object: &object,
Relation: &relation,
SubjectId: &subjectId,
}
// write
url, err := container.WriteAPIAddress(ctx)
if err != nil {
t.Fatal(err)
}
cw := newTestClient(t, url)
_, _, err = cw.RelationshipAPI.CreateRelationship(ctx).CreateRelationshipBody(payload).Execute()
if err != nil {
t.Fatalf("relationship: %v", err)
}
// read
url, err = container.ReadAPIAddress(ctx)
if err != nil {
t.Fatal(err)
}
cr := newTestClient(t, url)
check, _, err := cr.PermissionAPI.CheckPermission(ctx).
Namespace(namespace).
Object(object).
Relation(relation).
SubjectId(subjectId).Execute()
if err != nil {
t.Fatalf("permission: %v", err)
}
if !check.Allowed {
t.Fatalf("should be allowed")
}
}
func newTestClient(t testing.TB, url string) *ory.APIClient {
c := ory.NewConfiguration()
c.Servers = []ory.ServerConfiguration{{URL: url}}
return ory.NewAPIClient(c)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment