Monorepo for Tangled
tangled.org
1package validator
2
3import (
4 "context"
5 "encoding/json"
6 "errors"
7 "io"
8 "log/slog"
9 "net/http"
10 "net/http/httptest"
11 "path/filepath"
12 "strings"
13 "testing"
14 "time"
15
16 "tangled.org/core/api/tangled"
17 "tangled.org/core/appview/db"
18 "tangled.org/core/appview/knotacl"
19 "tangled.org/core/appview/models"
20 "tangled.org/core/consts"
21 "tangled.org/core/rbac"
22)
23
24func unreachableListValidator(t *testing.T) (*Validator, string) {
25 t.Helper()
26 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
27 switch {
28 case strings.HasSuffix(r.URL.Path, tangled.KnotVersionNSID):
29 json.NewEncoder(w).Encode(tangled.KnotVersion_Output{Version: "v1.15.0", Capabilities: []string{string(consts.CapKnotACL)}})
30 case strings.HasSuffix(r.URL.Path, tangled.RepoListCollaboratorsNSID):
31 http.Error(w, "list down", http.StatusInternalServerError)
32 default:
33 http.NotFound(w, r)
34 }
35 }))
36 t.Cleanup(srv.Close)
37 host := strings.TrimPrefix(srv.URL, "http://")
38
39 dir := t.TempDir()
40 enforcer, err := rbac.NewEnforcer(filepath.Join(dir, "rbac.db"))
41 if err != nil {
42 t.Fatalf("NewEnforcer: %v", err)
43 }
44 d, err := db.Make(context.Background(), filepath.Join(dir, "appview.db"))
45 if err != nil {
46 t.Fatalf("db.Make: %v", err)
47 }
48 svc := knotacl.NewService(enforcer, d, true, slog.New(slog.NewTextHandler(io.Discard, nil)))
49 return &Validator{acl: svc}, host
50}
51
52func TestValidateLabelOp_MalformedRejectedBeforePermCheck(t *testing.T) {
53 v, host := unreachableListValidator(t)
54 def := &models.LabelDefinition{Did: "did:plc:akshay", Rkey: "deadbeef"}
55 repo := &models.Repo{Did: "did:plc:akshay", Knot: host, RepoDid: "did:plc:limpet"}
56
57 op := &models.LabelOp{
58 Did: "did:plc:scallop",
59 OperandKey: "does-not-match-the-def-aturi",
60 Operation: "garbage",
61 }
62 err := v.ValidateLabelOp(context.Background(), def, repo, op)
63 if errors.Is(err, knotacl.ErrKnotUnreachable) {
64 t.Fatalf("malformed op returned ErrKnotUnreachable; structural validation did not run before the perm check")
65 }
66 if err == nil || !strings.Contains(err.Error(), "operand key") {
67 t.Fatalf("want a structural operand-key error, got %v", err)
68 }
69}
70
71func TestValidateLabelOp_WellFormedFailsOpenWhenKnotUnreachable(t *testing.T) {
72 v, host := unreachableListValidator(t)
73 def := &models.LabelDefinition{Did: "did:plc:akshay", Rkey: "deadbeef"}
74 repo := &models.Repo{Did: "did:plc:akshay", Knot: host, RepoDid: "did:plc:limpet"}
75
76 op := &models.LabelOp{
77 Did: "did:plc:scallop",
78 OperandKey: def.AtUri().String(),
79 Operation: models.LabelOperationAdd,
80 Subject: "at://did:plc:limpet/sh.tangled.repo.issue/abc123",
81 PerformedAt: time.Now(),
82 }
83 err := v.ValidateLabelOp(context.Background(), def, repo, op)
84 if !errors.Is(err, knotacl.ErrKnotUnreachable) {
85 t.Fatalf("well-formed op against an unreachable knot = %v, want ErrKnotUnreachable so the ingester fails open", err)
86 }
87}