Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions internal/guard/wasm_labels_success_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package guard

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/github/gh-aw-mcpg/internal/difc"
)

// labelAgentSuccessWasm exports "label_agent" + "memory". It writes the following
// JSON to the output buffer and returns its length, exercising the full success
// path of LabelAgent (including the final logging + return statement).
//
// {"difc_mode": "strict"}
//
// Compiled by hand: the function body stores each byte of the JSON into linear
// memory at outPtr (param 2) via a sequence of i32.const/i32.store8 pairs, then
// returns i32.const <len(json)>.
var labelAgentSuccessWasm = []byte{
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x09, 0x01, 0x60, 0x04, 0x7f, 0x7f, 0x7f,
0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x07, 0x18, 0x02, 0x0b,
0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x00, 0x00, 0x06, 0x6d, 0x65,
0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x0a, 0xb9, 0x01, 0x01, 0xb6, 0x01, 0x00, 0x20, 0x02, 0x41,
0xfb, 0x00, 0x3a, 0x00, 0x00, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x01, 0x20, 0x02, 0x41, 0xe4,
0x00, 0x3a, 0x00, 0x02, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00, 0x03, 0x20, 0x02, 0x41, 0xe6,
0x00, 0x3a, 0x00, 0x04, 0x20, 0x02, 0x41, 0xe3, 0x00, 0x3a, 0x00, 0x05, 0x20, 0x02, 0x41, 0xdf,
0x00, 0x3a, 0x00, 0x06, 0x20, 0x02, 0x41, 0xed, 0x00, 0x3a, 0x00, 0x07, 0x20, 0x02, 0x41, 0xef,
0x00, 0x3a, 0x00, 0x08, 0x20, 0x02, 0x41, 0xe4, 0x00, 0x3a, 0x00, 0x09, 0x20, 0x02, 0x41, 0xe5,
0x00, 0x3a, 0x00, 0x0a, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x0b, 0x20, 0x02, 0x41, 0x3a, 0x3a,
0x00, 0x0c, 0x20, 0x02, 0x41, 0x20, 0x3a, 0x00, 0x0d, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x0e,
0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00, 0x0f, 0x20, 0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x10,
0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x11, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00, 0x12,
0x20, 0x02, 0x41, 0xe3, 0x00, 0x3a, 0x00, 0x13, 0x20, 0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x14,
0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x15, 0x20, 0x02, 0x41, 0xfd, 0x00, 0x3a, 0x00, 0x16, 0x41,
0x17, 0x0b,
}

// labelResourceRespondsToCapabilitiesWasm exports "label_resource" + "memory".
// It is input-sensitive: the response it writes depends on the size of the input
// JSON, so the tests below fail if LabelResource stops injecting the
// "capabilities" key (or starts injecting it unconditionally).
//
// If inLen > 60 it writes (54 bytes):
//
// {"resource":{"description":"caps"},"operation":"read"}
//
// otherwise it writes (58 bytes):
//
// {"resource":{"description":"no-caps"},"operation":"write"}
//
// Input size reference (json.Marshal key-sorted output, toolName="some_tool",
// args={"arg":"value"}):
//
// baseline (caps == nil) 53 B – {"tool_args":…,"tool_name":…}
// + capabilities ({}) 71 B
// threshold 60 B
//
// Compiled from:
//
// (module
// (memory (export "memory") 1)
// (func (export "label_resource") (param i32 i32 i32 i32) (result i32)
// ;; params: inPtr inLen outPtr outLen
// local.get 1 i32.const 60 i32.gt_u
// if
// ;; store the "caps"/"read" JSON byte-by-byte at outPtr
// i32.const 54
// return
// end
// ;; store the "no-caps"/"write" JSON byte-by-byte at outPtr
// i32.const 58))
var labelResourceRespondsToCapabilitiesWasm = []byte{
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x09, 0x01, 0x60, 0x04, 0x7f, 0x7f, 0x7f,
0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x05, 0x03, 0x01, 0x00, 0x01, 0x07, 0x1b, 0x02, 0x06,
0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x0e, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x72,
0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x00, 0x00, 0x0a, 0xf5, 0x06, 0x01, 0xf2, 0x06, 0x00,
0x20, 0x01, 0x41, 0x3c, 0x4b, 0x04, 0x40, 0x20, 0x02, 0x41, 0xfb, 0x00, 0x3a, 0x00, 0x00, 0x20,
0x02, 0x41, 0x22, 0x3a, 0x00, 0x01, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x02, 0x20, 0x02,
0x41, 0xe5, 0x00, 0x3a, 0x00, 0x03, 0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00, 0x04, 0x20, 0x02,
0x41, 0xef, 0x00, 0x3a, 0x00, 0x05, 0x20, 0x02, 0x41, 0xf5, 0x00, 0x3a, 0x00, 0x06, 0x20, 0x02,
0x41, 0xf2, 0x00, 0x3a, 0x00, 0x07, 0x20, 0x02, 0x41, 0xe3, 0x00, 0x3a, 0x00, 0x08, 0x20, 0x02,
0x41, 0xe5, 0x00, 0x3a, 0x00, 0x09, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x0a, 0x20, 0x02, 0x41,
0x3a, 0x3a, 0x00, 0x0b, 0x20, 0x02, 0x41, 0xfb, 0x00, 0x3a, 0x00, 0x0c, 0x20, 0x02, 0x41, 0x22,
0x3a, 0x00, 0x0d, 0x20, 0x02, 0x41, 0xe4, 0x00, 0x3a, 0x00, 0x0e, 0x20, 0x02, 0x41, 0xe5, 0x00,
0x3a, 0x00, 0x0f, 0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00, 0x10, 0x20, 0x02, 0x41, 0xe3, 0x00,
0x3a, 0x00, 0x11, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x12, 0x20, 0x02, 0x41, 0xe9, 0x00,
0x3a, 0x00, 0x13, 0x20, 0x02, 0x41, 0xf0, 0x00, 0x3a, 0x00, 0x14, 0x20, 0x02, 0x41, 0xf4, 0x00,
0x3a, 0x00, 0x15, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00, 0x16, 0x20, 0x02, 0x41, 0xef, 0x00,
0x3a, 0x00, 0x17, 0x20, 0x02, 0x41, 0xee, 0x00, 0x3a, 0x00, 0x18, 0x20, 0x02, 0x41, 0x22, 0x3a,
0x00, 0x19, 0x20, 0x02, 0x41, 0x3a, 0x3a, 0x00, 0x1a, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x1b,
0x20, 0x02, 0x41, 0xe3, 0x00, 0x3a, 0x00, 0x1c, 0x20, 0x02, 0x41, 0xe1, 0x00, 0x3a, 0x00, 0x1d,
0x20, 0x02, 0x41, 0xf0, 0x00, 0x3a, 0x00, 0x1e, 0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00, 0x1f,
0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x20, 0x20, 0x02, 0x41, 0xfd, 0x00, 0x3a, 0x00, 0x21, 0x20,
0x02, 0x41, 0x2c, 0x3a, 0x00, 0x22, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x23, 0x20, 0x02, 0x41,
0xef, 0x00, 0x3a, 0x00, 0x24, 0x20, 0x02, 0x41, 0xf0, 0x00, 0x3a, 0x00, 0x25, 0x20, 0x02, 0x41,
0xe5, 0x00, 0x3a, 0x00, 0x26, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x27, 0x20, 0x02, 0x41,
0xe1, 0x00, 0x3a, 0x00, 0x28, 0x20, 0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x29, 0x20, 0x02, 0x41,
0xe9, 0x00, 0x3a, 0x00, 0x2a, 0x20, 0x02, 0x41, 0xef, 0x00, 0x3a, 0x00, 0x2b, 0x20, 0x02, 0x41,
0xee, 0x00, 0x3a, 0x00, 0x2c, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x2d, 0x20, 0x02, 0x41, 0x3a,
0x3a, 0x00, 0x2e, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x2f, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a,
0x00, 0x30, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a, 0x00, 0x31, 0x20, 0x02, 0x41, 0xe1, 0x00, 0x3a,
0x00, 0x32, 0x20, 0x02, 0x41, 0xe4, 0x00, 0x3a, 0x00, 0x33, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00,
0x34, 0x20, 0x02, 0x41, 0xfd, 0x00, 0x3a, 0x00, 0x35, 0x41, 0x36, 0x0f, 0x0b, 0x20, 0x02, 0x41,
0xfb, 0x00, 0x3a, 0x00, 0x00, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x01, 0x20, 0x02, 0x41, 0xf2,
0x00, 0x3a, 0x00, 0x02, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a, 0x00, 0x03, 0x20, 0x02, 0x41, 0xf3,
0x00, 0x3a, 0x00, 0x04, 0x20, 0x02, 0x41, 0xef, 0x00, 0x3a, 0x00, 0x05, 0x20, 0x02, 0x41, 0xf5,
0x00, 0x3a, 0x00, 0x06, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x07, 0x20, 0x02, 0x41, 0xe3,
0x00, 0x3a, 0x00, 0x08, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a, 0x00, 0x09, 0x20, 0x02, 0x41, 0x22,
0x3a, 0x00, 0x0a, 0x20, 0x02, 0x41, 0x3a, 0x3a, 0x00, 0x0b, 0x20, 0x02, 0x41, 0xfb, 0x00, 0x3a,
0x00, 0x0c, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x0d, 0x20, 0x02, 0x41, 0xe4, 0x00, 0x3a, 0x00,
0x0e, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a, 0x00, 0x0f, 0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00,
0x10, 0x20, 0x02, 0x41, 0xe3, 0x00, 0x3a, 0x00, 0x11, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00,
0x12, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00, 0x13, 0x20, 0x02, 0x41, 0xf0, 0x00, 0x3a, 0x00,
0x14, 0x20, 0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x15, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00,
0x16, 0x20, 0x02, 0x41, 0xef, 0x00, 0x3a, 0x00, 0x17, 0x20, 0x02, 0x41, 0xee, 0x00, 0x3a, 0x00,
0x18, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x19, 0x20, 0x02, 0x41, 0x3a, 0x3a, 0x00, 0x1a, 0x20,
0x02, 0x41, 0x22, 0x3a, 0x00, 0x1b, 0x20, 0x02, 0x41, 0xee, 0x00, 0x3a, 0x00, 0x1c, 0x20, 0x02,
0x41, 0xef, 0x00, 0x3a, 0x00, 0x1d, 0x20, 0x02, 0x41, 0x2d, 0x3a, 0x00, 0x1e, 0x20, 0x02, 0x41,
0xe3, 0x00, 0x3a, 0x00, 0x1f, 0x20, 0x02, 0x41, 0xe1, 0x00, 0x3a, 0x00, 0x20, 0x20, 0x02, 0x41,
0xf0, 0x00, 0x3a, 0x00, 0x21, 0x20, 0x02, 0x41, 0xf3, 0x00, 0x3a, 0x00, 0x22, 0x20, 0x02, 0x41,
0x22, 0x3a, 0x00, 0x23, 0x20, 0x02, 0x41, 0xfd, 0x00, 0x3a, 0x00, 0x24, 0x20, 0x02, 0x41, 0x2c,
0x3a, 0x00, 0x25, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x26, 0x20, 0x02, 0x41, 0xef, 0x00, 0x3a,
0x00, 0x27, 0x20, 0x02, 0x41, 0xf0, 0x00, 0x3a, 0x00, 0x28, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a,
0x00, 0x29, 0x20, 0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x2a, 0x20, 0x02, 0x41, 0xe1, 0x00, 0x3a,
0x00, 0x2b, 0x20, 0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x2c, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a,
0x00, 0x2d, 0x20, 0x02, 0x41, 0xef, 0x00, 0x3a, 0x00, 0x2e, 0x20, 0x02, 0x41, 0xee, 0x00, 0x3a,
0x00, 0x2f, 0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x30, 0x20, 0x02, 0x41, 0x3a, 0x3a, 0x00, 0x31,
0x20, 0x02, 0x41, 0x22, 0x3a, 0x00, 0x32, 0x20, 0x02, 0x41, 0xf7, 0x00, 0x3a, 0x00, 0x33, 0x20,
0x02, 0x41, 0xf2, 0x00, 0x3a, 0x00, 0x34, 0x20, 0x02, 0x41, 0xe9, 0x00, 0x3a, 0x00, 0x35, 0x20,
0x02, 0x41, 0xf4, 0x00, 0x3a, 0x00, 0x36, 0x20, 0x02, 0x41, 0xe5, 0x00, 0x3a, 0x00, 0x37, 0x20,
0x02, 0x41, 0x22, 0x3a, 0x00, 0x38, 0x20, 0x02, 0x41, 0xfd, 0x00, 0x3a, 0x00, 0x39, 0x41, 0x3a,
0x0b,
}

// TestLabelAgent_SuccessPath exercises the full happy path of LabelAgent, including
// the final response-parsing, marshaled-debug-logging, and return statements that
// were previously uncovered by existing error-path-only tests.
func TestLabelAgent_SuccessPath(t *testing.T) {
g, cleanup := setupRawWasmModule(t, labelAgentSuccessWasm, "label-agent-success")
defer cleanup()

validPolicy := map[string]any{
"allow-only": map[string]any{
"repos": "public",
"min-integrity": "none",
},
}
result, err := g.LabelAgent(context.Background(), validPolicy, &mockBackendCaller{}, nil)

require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, "strict", result.DIFCMode)
}

// TestLabelResource_SuccessPath exercises the full happy path of LabelResource,
// including the caps != nil branch and a successful parseResourceResponse call
// returning a populated resource and a non-default ("read") operation.
//
// labelResourceRespondsToCapabilitiesWasm only returns the "caps"/"read" response
// when the input JSON exceeds 60 bytes, which happens only once the "capabilities"
// key is added to the 53-byte baseline input. The assertions therefore fail if
// LabelResource stops injecting capabilities.
func TestLabelResource_SuccessPath(t *testing.T) {
g, cleanup := setupRawWasmModule(t, labelResourceRespondsToCapabilitiesWasm, "label-resource-success")
defer cleanup()

caps := difc.NewCapabilities()
resource, operation, err := g.LabelResource(
context.Background(),
"some_tool",
map[string]any{"arg": "value"},
&mockBackendCaller{},
caps,
)

require.NoError(t, err)
require.NotNil(t, resource)
assert.Equal(t, "caps", resource.Description, "capabilities must be included in the WASM input (inLen should exceed the 60-byte threshold)")
assert.Equal(t, difc.OperationRead, operation)
}

// TestLabelResource_SuccessPath_NilCapabilities verifies that LabelResource omits
// the "capabilities" key when caps is nil, covering the complementary branch. The
// input stays at the 53-byte baseline (below the fixture's 60-byte threshold), so
// the guard returns the distinct "no-caps"/"write" response.
func TestLabelResource_SuccessPath_NilCapabilities(t *testing.T) {
g, cleanup := setupRawWasmModule(t, labelResourceRespondsToCapabilitiesWasm, "label-resource-success-nil-caps")
defer cleanup()

resource, operation, err := g.LabelResource(
context.Background(),
"some_tool",
map[string]any{"arg": "value"},
&mockBackendCaller{},
nil,
)

require.NoError(t, err)
require.NotNil(t, resource)
assert.Equal(t, "no-caps", resource.Description, "nil capabilities must not add the capabilities key to the WASM input")
assert.Equal(t, difc.OperationWrite, operation)
}
Loading