fix/v02-patch: pr_files_diff, template_name, repo_update #26
@@ -45,14 +45,15 @@ func NewCreateProjectFromTemplate(c *gitea.Client, a *allowlist.Allowlist, tmplO
|
|||||||
func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor {
|
func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor {
|
||||||
return registry.ToolDescriptor{
|
return registry.ToolDescriptor{
|
||||||
Name: "create_project_from_template",
|
Name: "create_project_from_template",
|
||||||
Description: "Create a new project repo from the template, applying placeholder substitutions to known files.",
|
Description: "Create a new project repo from a template, applying placeholder substitutions to known files. Defaults to the server-configured template; pass template_name to override (e.g. template-go-agent).",
|
||||||
InputSchema: json.RawMessage(`{
|
InputSchema: json.RawMessage(`{
|
||||||
"type":"object",
|
"type":"object",
|
||||||
"properties":{
|
"properties":{
|
||||||
"owner":{"type":"string"},
|
"owner":{"type":"string"},
|
||||||
"name":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,38}[a-z0-9]$"},
|
"name":{"type":"string","pattern":"^[a-z][a-z0-9-]{1,38}[a-z0-9]$"},
|
||||||
"description":{"type":"string"},
|
"description":{"type":"string"},
|
||||||
"private":{"type":"boolean"}
|
"private":{"type":"boolean"},
|
||||||
|
"template_name":{"type":"string","description":"Template repo name to generate from. Defaults to the server-configured template."}
|
||||||
},
|
},
|
||||||
"required":["owner","name"]
|
"required":["owner","name"]
|
||||||
}`),
|
}`),
|
||||||
@@ -60,10 +61,11 @@ func (t *CreateProjectFromTemplate) Descriptor() registry.ToolDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type createProjectArgs struct {
|
type createProjectArgs struct {
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Private bool `json:"private"`
|
Private bool `json:"private"`
|
||||||
|
TemplateName string `json:"template_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type createProjectResult struct {
|
type createProjectResult struct {
|
||||||
@@ -91,13 +93,20 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag
|
|||||||
return nil, fmt.Errorf("name %q does not match pattern %s: %w", args.Name, nameRe.String(), gitea.ErrValidation)
|
return nil, fmt.Errorf("name %q does not match pattern %s: %w", args.Name, nameRe.String(), gitea.ErrValidation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resolve template: per-call override takes precedence over the
|
||||||
|
// server-configured default. Owner stays server-configured.
|
||||||
|
tmplName := args.TemplateName
|
||||||
|
if tmplName == "" {
|
||||||
|
tmplName = t.templateName
|
||||||
|
}
|
||||||
|
|
||||||
// Verify template exists and is marked as a template repo.
|
// Verify template exists and is marked as a template repo.
|
||||||
tmpl, err := t.c.GetRepo(ctx, t.templateOwner, t.templateName)
|
tmpl, err := t.c.GetRepo(ctx, t.templateOwner, tmplName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("template lookup: %w", err)
|
return nil, fmt.Errorf("template lookup: %w", err)
|
||||||
}
|
}
|
||||||
if !tmpl.Template {
|
if !tmpl.Template {
|
||||||
return nil, fmt.Errorf("repo %s/%s is not marked as template: %w", t.templateOwner, t.templateName, gitea.ErrValidation)
|
return nil, fmt.Errorf("repo %s/%s is not marked as template: %w", t.templateOwner, tmplName, gitea.ErrValidation)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify destination doesn't already exist.
|
// Verify destination doesn't already exist.
|
||||||
@@ -108,7 +117,7 @@ func (t *CreateProjectFromTemplate) Call(ctx context.Context, raw json.RawMessag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate repo from template.
|
// Generate repo from template.
|
||||||
newRepo, err := t.c.GenerateFromTemplate(ctx, t.templateOwner, t.templateName, gitea.GenerateFromTemplateArgs{
|
newRepo, err := t.c.GenerateFromTemplate(ctx, t.templateOwner, tmplName, gitea.GenerateFromTemplateArgs{
|
||||||
Owner: args.Owner,
|
Owner: args.Owner,
|
||||||
Name: args.Name,
|
Name: args.Name,
|
||||||
Description: args.Description,
|
Description: args.Description,
|
||||||
|
|||||||
@@ -122,6 +122,62 @@ func TestCreateProjectHappyPath(t *testing.T) {
|
|||||||
assert.Empty(t, out.PartialFailure)
|
assert.Empty(t, out.PartialFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCreateProjectTemplateNameOverride (issue #24): per-call template_name overrides the
|
||||||
|
// server-configured default, so the same binary can generate from template-go-web or
|
||||||
|
// template-go-agent without restart.
|
||||||
|
func TestCreateProjectTemplateNameOverride(t *testing.T) {
|
||||||
|
var templateLookups, generateCalls []string
|
||||||
|
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
switch {
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/mathias/template-go-agent":
|
||||||
|
templateLookups = append(templateLookups, "template-go-agent")
|
||||||
|
_, _ = w.Write([]byte(newTemplateRepoJSON("template-go-agent", true)))
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/mathias/template-go-web":
|
||||||
|
templateLookups = append(templateLookups, "template-go-web")
|
||||||
|
_, _ = w.Write([]byte(newTemplateRepoJSON("template-go-web", true)))
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && r.URL.Path == "/api/v1/repos/mathias/new-agent":
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
_, _ = w.Write([]byte(`{"message":"not found"}`))
|
||||||
|
|
||||||
|
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/generate"):
|
||||||
|
generateCalls = append(generateCalls, r.URL.Path)
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_, _ = w.Write([]byte(newGeneratedRepoJSON("new-agent")))
|
||||||
|
|
||||||
|
case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v1/repos/mathias/new-agent/contents/"):
|
||||||
|
filePath := strings.TrimPrefix(r.URL.Path, "/api/v1/repos/mathias/new-agent/contents/")
|
||||||
|
_, _ = w.Write([]byte(fileContentsJSON(filePath)))
|
||||||
|
|
||||||
|
case r.Method == http.MethodPut && strings.HasPrefix(r.URL.Path, "/api/v1/repos/mathias/new-agent/contents/"):
|
||||||
|
filePath := strings.TrimPrefix(r.URL.Path, "/api/v1/repos/mathias/new-agent/contents/")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte(fileWriteResultJSON(filePath)))
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
// Server is configured with template-go-web as the default; call overrides to template-go-agent.
|
||||||
|
tool := newCreateProjectTool(srv.URL)
|
||||||
|
_, err := tool.Call(context.Background(), json.RawMessage(
|
||||||
|
`{"owner":"mathias","name":"new-agent","template_name":"template-go-agent"}`,
|
||||||
|
))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"template-go-agent"}, templateLookups,
|
||||||
|
"override must direct the template lookup, not the server default")
|
||||||
|
require.Len(t, generateCalls, 1)
|
||||||
|
assert.Equal(t, "/api/v1/repos/mathias/template-go-agent/generate", generateCalls[0],
|
||||||
|
"override must direct the /generate call too")
|
||||||
|
}
|
||||||
|
|
||||||
// TestCreateProjectNameRegexFailure: invalid name returns ErrValidation without hitting network.
|
// TestCreateProjectNameRegexFailure: invalid name returns ErrValidation without hitting network.
|
||||||
func TestCreateProjectNameRegexFailure(t *testing.T) {
|
func TestCreateProjectNameRegexFailure(t *testing.T) {
|
||||||
tool := tools.NewCreateProjectFromTemplate(
|
tool := tools.NewCreateProjectFromTemplate(
|
||||||
|
|||||||
Reference in New Issue
Block a user