Files
coder/coderd/database/user_skills_test.go
Michael Suchacz 5a8d0016a5 feat: add personal skill storage, API, and SDK (#25363)
> Mux updated this PR on behalf of Mike.

## Stack Context

This PR is the storage, permissions, API, and SDK layer for experimental
personal skills. #25362 has landed on `main`, so this branch is
restacked directly on `main`.

Stack order:
1. #25363 storage, permissions, API, and SDK
2. #25365 API test coverage
3. #25366 chattool and chatd integration
4. #25066 settings UI and docs
5. #25386 personal skills slash menu

## What?

Adds the `user_skills` database table, generated queries, RBAC resources
and scopes, audit resource handling, experimental user-scoped CRUD
endpoints, SDK types, and generated API/site types.

Follow-up review and restack fixes:
- Enforce a bounded personal skill description in parser and database
constraints.
- Return `403 Forbidden` for unauthorized create and update attempts.
- Return explicit conflict responses when soft-deleted users are
targeted.
- Keep user admins out of personal skills, while site owners can read
and delete but not create or update.
- Document trigger-raised constraint names and keep schema constants
covered by tests.
- Reuse `UserSkillMetadata` in the full `UserSkill` SDK response type.
- Generate user skill IDs in Go instead of relying on a database
default.
- Rebase on latest `main` and renumber the user skills migration to
`000502_user_skills`.

## Why?

Personal skills need durable user-owned storage with owner
authorization, limited site-owner moderation, and a hidden API surface
before chatd can consume them.

## Validation

- `make gen`
- `go test ./coderd/database -run '^TestUserSkillSchemaConstants$'
-count=1`
- `go test ./coderd/database/dbauthz -run
'^TestMethodTestSuite/TestUserSkills$' -count=1`
- `go test ./coderd -run '^TestPatchUserSkill$' -count=1`
- `go test ./codersdk ./coderd/database/db2sdk`
- `make lint`
- pre-commit hook on `97fd58108d`
2026-05-20 00:09:09 +02:00

63 lines
1.7 KiB
Go

package database_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbtestutil"
"github.com/coder/coder/v2/coderd/x/skills"
"github.com/coder/coder/v2/testutil"
)
func TestUserSkillSchemaConstants(t *testing.T) {
t.Parallel()
if testing.Short() {
t.SkipNow()
}
ctx := testutil.Context(t, testutil.WaitMedium)
_, _, sqlDB := dbtestutil.NewDBWithSQLDB(t)
var triggerDef string
err := sqlDB.QueryRowContext(ctx,
`SELECT pg_get_functiondef('enforce_user_skills_per_user_limit'::regproc)`,
).Scan(&triggerDef)
require.NoError(t, err)
require.Contains(t, triggerDef, fmt.Sprintf(
"skill_limit constant int := %d",
skills.MaxPersonalSkillsPerUser,
))
constraints := map[database.CheckConstraint]string{
database.CheckUserSkillsNameSize: fmt.Sprintf(
"octet_length(name) <= %d",
skills.MaxPersonalSkillNameBytes,
),
database.CheckUserSkillsNameFormat: "name ~ '^[a-z0-9]+(-[a-z0-9]+)*$'::text",
database.CheckUserSkillsDescriptionSize: fmt.Sprintf(
"octet_length(description) <= %d",
skills.MaxPersonalSkillDescriptionBytes,
),
database.CheckUserSkillsContentSize: fmt.Sprintf(
"octet_length(content) <= %d",
skills.MaxPersonalSkillSizeBytes,
),
}
for constraint, expected := range constraints {
t.Run(string(constraint), func(t *testing.T) {
t.Parallel()
ctx := testutil.Context(t, testutil.WaitMedium)
var constraintDef string
err := sqlDB.QueryRowContext(ctx,
`SELECT pg_get_constraintdef(oid) FROM pg_constraint WHERE conname = $1`,
constraint,
).Scan(&constraintDef)
require.NoError(t, err)
require.Contains(t, constraintDef, expected)
})
}
}