Files
ucan/token/delegation/delegationtest/generator_test.go
Steve Moyer 1098e76cba test(invocation): add command.Covers and subject consistency tests
Also improve the maintainability of the tests by a) providing a set of fixed Personas
and then generating a slew of valid delegation tokens, invalid delegation tokens
and proof-chains thereof.
2024-11-19 14:35:46 -05:00

225 lines
5.3 KiB
Go

package delegationtest
import (
"os"
"path/filepath"
"slices"
"testing"
"time"
"github.com/dave/jennifer/jen"
"github.com/ipfs/go-cid"
"github.com/libp2p/go-libp2p/core/crypto"
"github.com/stretchr/testify/require"
"github.com/ucan-wg/go-ucan/did"
"github.com/ucan-wg/go-ucan/did/didtest"
"github.com/ucan-wg/go-ucan/pkg/command"
"github.com/ucan-wg/go-ucan/pkg/policy"
"github.com/ucan-wg/go-ucan/pkg/policy/policytest"
"github.com/ucan-wg/go-ucan/token/delegation"
"gotest.tools/v3/golden"
)
const (
tokenNamePrefix = "Token"
proorChainNamePrefix = "Proof"
)
var constantNonce = []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b}
// TestUpdate doesn't actually run a test but uses the Go testing library
// to trigger generation of the delegation tokens and associated Go file.
func TestUpdate(t *testing.T) {
if golden.FlagUpdate() {
update(t)
}
}
type newDelegationParams struct {
privKey crypto.PrivKey
aud did.DID
sub did.DID
cmd command.Command
pol policy.Policy
opts []delegation.Option
}
type newDelegationParamsVariant func(*newDelegationParams)
type token struct {
name string
id cid.Cid
}
type proof struct {
name string
prf []cid.Cid
}
type generator struct {
dlgs []token
chains []proof
}
type acc struct {
name string
chain []cid.Cid
}
type variant struct {
name string
variant func(*newDelegationParams)
}
func noopVariant() variant {
return variant{
name: "",
variant: func(_ *newDelegationParams) {},
}
}
func update(t *testing.T) {
t.Helper()
gen := &generator{}
gen.chainPersonas(t, didtest.Personas(t), acc{}, noopVariant())
gen.writeGoFile(t)
}
func (g *generator) chainPersonas(t *testing.T, personas []didtest.Persona, acc acc, vari variant) {
t.Helper()
acc.name += personas[0].Name(t)
g.createProofChain(t, acc.name+vari.name, acc.chain)
if len(personas) < 2 {
return
}
name := personas[0].Name(t) + personas[1].Name(t)
params := newDelegationParams{
privKey: personas[0].PrivKey(t),
aud: personas[1].DID(t),
cmd: NominalCommand,
pol: policytest.EmptyPolicy(t),
opts: []delegation.Option{
delegation.WithSubject(didtest.PersonaAlice.DID(t)),
delegation.WithNonce(constantNonce),
},
}
// Create each nominal token and continue the chain
id := g.createDelegation(t, params, name, vari)
acc.chain = append(acc.chain, id)
g.chainPersonas(t, personas[1:], acc, vari)
// If the user is Carol, create variants for each invalid and/or optional
// parameter and also continue the chain
if personas[0] == didtest.PersonaCarol {
variants := []variant{
{name: "InvalidExpandedCommand", variant: func(p *newDelegationParams) {
p.cmd = ExpandedCommand
}},
{name: "ValidAttenuatedCommand", variant: func(p *newDelegationParams) {
p.cmd = AttenuatedCommand
}},
{name: "InvalidSubject", variant: func(p *newDelegationParams) {
p.opts = append(p.opts, delegation.WithSubject(didtest.PersonaBob.DID(t)))
}},
{name: "InvalidExpired", variant: func(p *newDelegationParams) {
p.opts = append(p.opts, delegation.WithExpiration(time.Now().Add(time.Second)))
}},
{name: "InvalidInactive", variant: func(p *newDelegationParams) {
nbf, err := time.Parse(time.RFC3339, "2070-01-01T00:00:00Z")
require.NoError(t, err)
p.opts = append(p.opts, delegation.WithNotBefore(nbf))
}},
}
// Start a branch in the recursion for each of the variants
for _, v := range variants {
id := g.createDelegation(t, params, name, v)
// replace the previous Carol token id with the one from the variant
acc.chain[len(acc.chain)-1] = id
g.chainPersonas(t, personas[1:], acc, v)
}
}
}
func (g *generator) createDelegation(t *testing.T, params newDelegationParams, name string, vari variant) cid.Cid {
t.Helper()
vari.variant(&params)
tkn, err := delegation.New(params.privKey, params.aud, params.cmd, params.pol, params.opts...)
require.NoError(t, err)
data, id, err := tkn.ToSealed(params.privKey)
require.NoError(t, err)
require.NoError(t, os.WriteFile(filepath.Join(tokenDir, tokenNamePrefix+name+vari.name+tokenExt), data, 0o644))
g.dlgs = append(g.dlgs, token{
name: tokenNamePrefix + name + vari.name,
id: id,
})
return id
}
func (g *generator) createProofChain(t *testing.T, name string, prf []cid.Cid) {
t.Helper()
if len(prf) < 1 {
return
}
clone := make([]cid.Cid, len(prf))
copy(clone, prf)
g.chains = append(g.chains, proof{
name: proorChainNamePrefix + name,
prf: clone,
})
}
func (g *generator) writeGoFile(t *testing.T) {
t.Helper()
file := jen.NewFile("delegationtest")
file.HeaderComment("Code generated by delegationtest - DO NOT EDIT.")
refs := map[cid.Cid]string{}
for _, d := range g.dlgs {
refs[d.id] = d.name + "CID"
file.Var().Defs(
jen.Id(d.name+"CID").Op("=").Qual("github.com/ipfs/go-cid", "MustParse").Call(jen.Lit(d.id.String())),
jen.Id(d.name).Op("=").Id("mustGetDelegation").Call(jen.Id(d.name+"CID")),
)
file.Line()
}
for _, c := range g.chains {
g := jen.CustomFunc(jen.Options{
Multi: true,
Separator: ",",
Close: "\n",
}, func(g *jen.Group) {
slices.Reverse(c.prf)
for _, p := range c.prf {
g.Id(refs[p])
}
})
file.Var().Id(c.name).Op("=").Index().Qual("github.com/ipfs/go-cid", "Cid").Values(g)
file.Line()
}
require.NoError(t, file.Save("token_gen.go"))
}