package security import ( "fmt" "testing" "gotest.tools/v3/assert" "gotest.tools/v3/assert/cmp" ) func TestDecode(t *testing.T) { tests := []struct { name string opts []string want []Option wantErr string }{ { name: "empty options", opts: []string{}, want: []Option{}, }, { name: "nil options", opts: nil, want: []Option{}, }, { name: "legacy format without equals", opts: []string{"apparmor:unconfined"}, want: []Option{ {Name: "apparmor:unconfined"}, }, }, { name: "single option with name only", opts: []string{"name=apparmor"}, want: []Option{ {Name: "apparmor"}, }, }, { name: "single option with name and additional options", opts: []string{"name=selinux,type=container_t,level=s0:c1.c2"}, want: []Option{ { Name: "selinux", Options: []KeyValue{ {Key: "type", Value: "container_t"}, {Key: "level", Value: "s0:c1.c2"}, }, }, }, }, { name: "multiple options", opts: []string{ "name=apparmor,profile=docker-default", "name=seccomp,profile=unconfined", }, want: []Option{ { Name: "apparmor", Options: []KeyValue{ {Key: "profile", Value: "docker-default"}, }, }, { Name: "seccomp", Options: []KeyValue{ {Key: "profile", Value: "unconfined"}, }, }, }, }, { name: "mixed legacy and new format", opts: []string{ "label:disable", "name=apparmor,profile=custom", }, want: []Option{ {Name: "label:disable"}, { Name: "apparmor", Options: []KeyValue{ {Key: "profile", Value: "custom"}, }, }, }, }, { name: "option without name key", opts: []string{"profile=custom,type=container_t"}, want: []Option{ { Options: []KeyValue{ {Key: "profile", Value: "custom"}, {Key: "type", Value: "container_t"}, }, }, }, }, { name: "option with equals in value", opts: []string{"name=selinux,level=s0:c1=c2"}, want: []Option{ { Name: "selinux", Options: []KeyValue{ {Key: "level", Value: "s0:c1=c2"}, }, }, }, }, { name: "invalid option without equals in comma-separated list", opts: []string{"name=apparmor,invalid"}, wantErr: `invalid security option "invalid"`, }, { name: "empty key", opts: []string{"=value"}, wantErr: "invalid empty security option", }, { name: "empty value", opts: []string{"key="}, wantErr: "invalid empty security option", }, { name: "empty key and value", opts: []string{"="}, wantErr: "invalid empty security option", }, { name: "empty key in middle", opts: []string{"name=apparmor,=value"}, wantErr: "invalid empty security option", }, { name: "empty value in middle", opts: []string{"name=apparmor,key="}, wantErr: "invalid empty security option", }, { name: "complex real-world example", opts: []string{ "name=selinux,user=system_u,role=system_r,type=container_t,level=s0:c1.c2", "name=apparmor,profile=/usr/bin/docker", "name=seccomp,profile=builtin", }, want: []Option{ { Name: "selinux", Options: []KeyValue{ {Key: "user", Value: "system_u"}, {Key: "role", Value: "system_r"}, {Key: "type", Value: "container_t"}, {Key: "level", Value: "s0:c1.c2"}, }, }, { Name: "apparmor", Options: []KeyValue{ {Key: "profile", Value: "/usr/bin/docker"}, }, }, { Name: "seccomp", Options: []KeyValue{ {Key: "profile", Value: "builtin"}, }, }, }, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { got, err := DecodeOptions(tc.opts) if tc.wantErr == "" { assert.NilError(t, err) assert.Check(t, cmp.DeepEqual(got, tc.want)) } else { assert.Check(t, err != nil, "expected error but got none") assert.ErrorContains(t, err, tc.wantErr) } }) } } func BenchmarkDecode(b *testing.B) { opts := []string{ "name=selinux,user=system_u,role=system_r,type=container_t,level=s0:c1.c2", "name=apparmor,profile=/usr/bin/docker", "name=seccomp,profile=builtin", "legacy:format", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := DecodeOptions(opts) if err != nil { b.Fatal(err) } } } func BenchmarkDecodeLegacy(b *testing.B) { opts := []string{ "apparmor:unconfined", "label:disable", "seccomp:unconfined", } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := DecodeOptions(opts) if err != nil { b.Fatal(err) } } } func BenchmarkDecodeComplex(b *testing.B) { opts := make([]string, 100) for i := range opts { opts[i] = fmt.Sprintf("name=test%d,key1=value1,key2=value2,key3=value3", i) } b.ResetTimer() for i := 0; i < b.N; i++ { _, err := DecodeOptions(opts) if err != nil { b.Fatal(err) } } }