diff --git a/e2e_test.go b/e2e_test.go index ae9df87..a25516e 100644 --- a/e2e_test.go +++ b/e2e_test.go @@ -943,4 +943,48 @@ func TestE2E(t *testing.T) { } }) }) + + t.Run("Namespace_ExplicitCreate", func(t *testing.T) { + nsDir, err := os.MkdirTemp("", "ax-ns-*") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(nsDir) + if err := exec.Command("cp", "./ax", filepath.Join(nsDir, "ax")).Run(); err != nil { + t.Fatal(err) + } + env := &testEnv{t: t, dir: nsDir, user: "testuser"} + env.mustAx("init") + + // Explicitly create a namespace node via --type namespace. + nsNode := env.parseNode(env.mustAx("add", "myworkspace", "--type", "namespace", "--json")) + + // The namespace should be in its own namespace (self-reference). + if !nsNode.HasRelation("in_namespace", nsNode.ID) { + t.Errorf("expected namespace to have in_namespace pointing to itself, got relations: %v", nsNode.Relations) + } + + // The creator should have write access to the new namespace. + users := env.parseNodes(env.mustAx("list", "--type", "user", "--json")) + var userNode *NodeResponse + for i := range users { + if users[i].Title == "testuser" { + userNode = &users[i] + break + } + } + if userNode == nil { + t.Fatal("could not find testuser node") + } + if !userNode.HasRelation("has_write_access", nsNode.ID) { + t.Errorf("expected creator to have has_write_access to new namespace, got relations: %v", userNode.Relations) + } + + // Nodes added to the new namespace should be accessible. + env.mustAx("add", "task in workspace", "--namespace", "myworkspace", "--json") + listed := env.parseNodes(env.mustAx("list", "--namespace", "myworkspace", "--json")) + if len(listed) == 0 { + t.Error("expected to list nodes in newly created namespace") + } + }) } diff --git a/service/node_service_impl.go b/service/node_service_impl.go index 5be4152..dd27153 100644 --- a/service/node_service_impl.go +++ b/service/node_service_impl.go @@ -328,6 +328,30 @@ func (s *nodeServiceImpl) Add(input AddInput) (*models.Node, error) { } } + // Namespace bootstrap: when creating a namespace node directly, apply the + // same setup as ensureNamespace — self in_namespace and creator write access. + if tmp.GetProperty("type") == "namespace" { + if !hasNamespace { + // Replace the default namespace rel (user's ns) with self-reference. + userNsID, _ := s.resolveIDByNameAndType(st, s.userID, "namespace") + if userNsID != "" { + if err := st.RemoveRel(id, string(models.RelInNamespace), userNsID); err != nil { + return err + } + } + if err := st.AddRel(id, string(models.RelInNamespace), id); err != nil { + return err + } + } + creatorID, err := s.resolveUserRef(st, s.userID) + if err != nil { + return err + } + if err := st.AddRel(creatorID, string(models.RelHasWriteAccess), id); err != nil { + return err + } + } + return nil }) if err != nil {