Skip to content

Commit 023f59d

Browse files
Use typed tool handler for get_me tool
1 parent 7e026fc commit 023f59d

File tree

23 files changed

+1153
-96
lines changed

23 files changed

+1153
-96
lines changed

docs/testing.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Testing
2+
3+
This project uses a combination of unit tests and end-to-end (e2e) tests to ensure correctness and stability.
4+
5+
## Unit Testing Patterns
6+
7+
- Unit tests are located alongside implementation, with filenames ending in `_test.go`.
8+
- Currently the preference is to use internal tests i.e. test files do not have `_test` package suffix.
9+
- Tests use [testify](https://github.com/stretchr/testify) for assertions and require statements. Use `require` when continuing the test is not meaningful, for example it is almost never correct to continue after an error expectation.
10+
- Mocking is performed using [go-github-mock](https://github.com/migueleliasweb/go-github-mock) or `githubv4mock` for simulating GitHub rest and GQL API responses.
11+
- Each tool's schema is snapshotted and checked for changes using the `toolsnaps` utility (see below).
12+
- Tests are designed to be explicit and verbose to aid maintainability and clarity.
13+
- Handler unit tests should take the form of:
14+
1. Test tool snapshot
15+
1. Very important expectations against the schema (e.g. `ReadOnly` annotation)
16+
1. Behavioural tests in table-driven form
17+
18+
## End-to-End (e2e) Tests
19+
20+
- E2E tests are located in the [`e2e/`](../e2e/) directory. See the [e2e/README.md](../e2e/README.md) for full details on running and debugging these tests.
21+
22+
## toolsnaps: Tool Schema Snapshots
23+
24+
- The `toolsnaps` utility ensures that the JSON schema for each tool does not change unexpectedly.
25+
- Snapshots are stored in `__toolsnaps__/*.snap` files , where `*` represents the name of the tool
26+
- When running tests, the current tool schema is compared to the snapshot. If there is a difference, the test will fail and show a diff.
27+
- If you intentionally change a tool's schema, update the snapshots by running tests with the environment variable: `UPDATE_TOOLSNAPS=true go test ./...`
28+
- In CI (when `GITHUB_ACTIONS=true`), missing snapshots will cause a test failure to ensure snapshots are always
29+
committed.
30+
31+
## Notes
32+
33+
- Some tools that mutate global state (e.g., marking all notifications as read) are tested primarily with unit tests, not e2e, to avoid side effects.
34+
- For more on the limitations and philosophy of the e2e suite, see the [e2e/README.md](../e2e/README.md).

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.23.7
44

55
require (
66
github.com/google/go-github/v69 v69.2.0
7+
github.com/josephburnett/jd v1.9.2
78
github.com/mark3labs/mcp-go v0.30.0
89
github.com/migueleliasweb/go-github-mock v1.3.0
910
github.com/sirupsen/logrus v1.9.3
@@ -12,6 +13,16 @@ require (
1213
github.com/stretchr/testify v1.10.0
1314
)
1415

16+
require (
17+
github.com/go-openapi/jsonpointer v0.19.5 // indirect
18+
github.com/go-openapi/swag v0.21.1 // indirect
19+
github.com/josharian/intern v1.0.0 // indirect
20+
github.com/mailru/easyjson v0.7.7 // indirect
21+
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
22+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
23+
gopkg.in/yaml.v2 v2.4.0 // indirect
24+
)
25+
1526
require (
1627
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
1728
github.com/fsnotify/fsnotify v1.8.0 // indirect

go.sum

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
2+
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
23
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
45
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -7,6 +8,11 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
78
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
89
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
910
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
11+
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
12+
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
13+
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
14+
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
15+
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
1016
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
1117
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
1218
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -24,17 +30,28 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
2430
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
2531
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
2632
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
33+
github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y=
34+
github.com/josephburnett/jd v1.9.2/go.mod h1:bImDr8QXpxMb3SD+w1cDRHp97xP6UwI88xUAuxwDQfM=
35+
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
36+
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
37+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
2738
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
2839
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
2940
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
3041
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
3142
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
3243
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
3344
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
45+
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
46+
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
47+
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
48+
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
49+
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
3450
github.com/mark3labs/mcp-go v0.30.0 h1:Taz7fiefkxY/l8jz1nA90V+WdM2eoMtlvwfWforVYbo=
3551
github.com/mark3labs/mcp-go v0.30.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
3652
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
3753
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
54+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
3855
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
3956
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
4057
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -64,15 +81,21 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
6481
github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
6582
github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
6683
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
84+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
85+
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6786
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
6887
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
6988
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
7089
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
7190
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
7291
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
7392
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
93+
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
94+
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
7495
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
7596
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
97+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
98+
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
7699
golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98=
77100
golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
78101
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -84,8 +107,14 @@ golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
84107
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
85108
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
86109
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
110+
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
111+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
87112
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
88113
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
114+
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
115+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
116+
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
89117
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
118+
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
90119
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
91120
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

internal/toolsnaps/toolsnaps.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Package toolsnaps provides test utilities for ensuring json schemas for tools
2+
// have not changed unexpectedly.
3+
package toolsnaps
4+
5+
import (
6+
"encoding/json"
7+
"fmt"
8+
"os"
9+
"path/filepath"
10+
11+
"github.com/josephburnett/jd/v2"
12+
)
13+
14+
// Test checks that the JSON schema for a tool has not changed unexpectedly.
15+
// It compares the marshaled JSON of the provided tool against a stored snapshot file.
16+
// If the UPDATE_TOOLSNAPS environment variable is set to "true", it updates the snapshot file instead.
17+
// If the snapshot does not exist and not running in CI, it creates the snapshot file.
18+
// If the snapshot does not exist and running in CI (GITHUB_ACTIONS="true"), it returns an error.
19+
// If the snapshot exists, it compares the tool's JSON to the snapshot and returns an error if they differ.
20+
// Returns an error if marshaling, reading, or comparing fails.
21+
func Test(toolName string, tool any) error {
22+
toolJSON, err := json.MarshalIndent(tool, "", " ")
23+
if err != nil {
24+
return fmt.Errorf("failed to marshal tool %s: %w", toolName, err)
25+
}
26+
27+
snapPath := fmt.Sprintf("__toolsnaps__/%s.snap", toolName)
28+
29+
// If UPDATE_TOOLSNAPS is set, then we write the tool JSON to the snapshot file and exit
30+
if os.Getenv("UPDATE_TOOLSNAPS") == "true" {
31+
return writeSnap(snapPath, toolJSON)
32+
}
33+
34+
snapJSON, err := os.ReadFile(snapPath) //nolint:gosec // filepaths are controlled by the test suite, so this is safe.
35+
// If the snapshot file does not exist, this must be the first time this test is run.
36+
// We write the tool JSON to the snapshot file and exit.
37+
if os.IsNotExist(err) {
38+
// If we're running in CI, we will error if there is not snapshot because it's important that snapshots
39+
// are committed alongside the tests, rather than just being constructed and not committed during a CI run.
40+
if os.Getenv("GITHUB_ACTIONS") == "true" {
41+
return fmt.Errorf("tool snapshot does not exist for %s. Please run the tests with UPDATE_TOOLSNAPS=true to create it", toolName)
42+
}
43+
44+
return writeSnap(snapPath, toolJSON)
45+
}
46+
47+
// Otherwise we will compare the tool JSON to the snapshot JSON
48+
toolNode, err := jd.ReadJsonString(string(toolJSON))
49+
if err != nil {
50+
return fmt.Errorf("failed to parse tool JSON for %s: %w", toolName, err)
51+
}
52+
53+
snapNode, err := jd.ReadJsonString(string(snapJSON))
54+
if err != nil {
55+
return fmt.Errorf("failed to parse snapshot JSON for %s: %w", toolName, err)
56+
}
57+
58+
// jd.Set allows arrays to be compared without order sensitivity,
59+
// which is useful because we don't really care about this when exposing tool schemas.
60+
diff := toolNode.Diff(snapNode, jd.SET).Render()
61+
if diff != "" {
62+
// If there is a difference, we return an error with the diff
63+
return fmt.Errorf("tool schema for %s has changed unexpectedly:\n%s", toolName, diff)
64+
}
65+
66+
return nil
67+
}
68+
69+
func writeSnap(snapPath string, contents []byte) error {
70+
// Ensure the directory exists
71+
if err := os.MkdirAll(filepath.Dir(snapPath), 0700); err != nil {
72+
return fmt.Errorf("failed to create snapshot directory: %w", err)
73+
}
74+
75+
// Write the snapshot file
76+
if err := os.WriteFile(snapPath, contents, 0600); err != nil {
77+
return fmt.Errorf("failed to write snapshot file: %w", err)
78+
}
79+
80+
return nil
81+
}

internal/toolsnaps/toolsnaps_test.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package toolsnaps
2+
3+
import (
4+
"encoding/json"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/require"
11+
)
12+
13+
type dummyTool struct {
14+
Name string `json:"name"`
15+
Value int `json:"value"`
16+
}
17+
18+
// withIsolatedWorkingDir creates a temp dir, changes to it, and restores the original working dir after the test.
19+
func withIsolatedWorkingDir(t *testing.T) {
20+
dir := t.TempDir()
21+
origDir, err := os.Getwd()
22+
require.NoError(t, err)
23+
t.Cleanup(func() { require.NoError(t, os.Chdir(origDir)) })
24+
require.NoError(t, os.Chdir(dir))
25+
}
26+
27+
func TestSnapshotDoesNotExistNotInCI(t *testing.T) {
28+
withIsolatedWorkingDir(t)
29+
30+
// Given we are not running in CI
31+
t.Setenv("GITHUB_ACTIONS", "false") // This REALLY is required because the tests run in CI
32+
tool := dummyTool{"foo", 42}
33+
34+
// When we test the snapshot
35+
err := Test("dummy", tool)
36+
37+
// Then it should succeed and write the snapshot file
38+
require.NoError(t, err)
39+
path := filepath.Join("__toolsnaps__", "dummy.snap")
40+
_, statErr := os.Stat(path)
41+
assert.NoError(t, statErr, "expected snapshot file to be written")
42+
}
43+
44+
func TestSnapshotDoesNotExistInCI(t *testing.T) {
45+
withIsolatedWorkingDir(t)
46+
47+
// Given we are running in CI
48+
t.Setenv("GITHUB_ACTIONS", "true")
49+
tool := dummyTool{"foo", 42}
50+
51+
// When we test the snapshot
52+
err := Test("dummy", tool)
53+
54+
// Then it should error about missing snapshot in CI
55+
require.Error(t, err)
56+
assert.Contains(t, err.Error(), "tool snapshot does not exist", "expected error about missing snapshot in CI")
57+
}
58+
59+
func TestSnapshotExistsMatch(t *testing.T) {
60+
withIsolatedWorkingDir(t)
61+
62+
// Given a matching snapshot file exists
63+
tool := dummyTool{"foo", 42}
64+
b, _ := json.MarshalIndent(tool, "", " ")
65+
require.NoError(t, os.MkdirAll("__toolsnaps__", 0700))
66+
require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), b, 0600))
67+
68+
// When we test the snapshot
69+
err := Test("dummy", tool)
70+
71+
// Then it should succeed (no error)
72+
require.NoError(t, err)
73+
}
74+
75+
func TestSnapshotExistsDiff(t *testing.T) {
76+
withIsolatedWorkingDir(t)
77+
78+
// Given a non-matching snapshot file exists
79+
require.NoError(t, os.MkdirAll("__toolsnaps__", 0700))
80+
require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600))
81+
tool := dummyTool{"foo", 2}
82+
83+
// When we test the snapshot
84+
err := Test("dummy", tool)
85+
86+
// Then it should error about the schema diff
87+
require.Error(t, err)
88+
assert.Contains(t, err.Error(), "tool schema for dummy has changed unexpectedly", "expected error about diff")
89+
}
90+
91+
func TestUpdateToolsnaps(t *testing.T) {
92+
withIsolatedWorkingDir(t)
93+
94+
// Given UPDATE_TOOLSNAPS is set, regardless of whether a matching snapshot file exists
95+
t.Setenv("UPDATE_TOOLSNAPS", "true")
96+
require.NoError(t, os.MkdirAll("__toolsnaps__", 0700))
97+
require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`{"name":"foo","value":1}`), 0600))
98+
tool := dummyTool{"foo", 42}
99+
100+
// When we test the snapshot
101+
err := Test("dummy", tool)
102+
103+
// Then it should succeed and write the snapshot file
104+
require.NoError(t, err)
105+
path := filepath.Join("__toolsnaps__", "dummy.snap")
106+
_, statErr := os.Stat(path)
107+
assert.NoError(t, statErr, "expected snapshot file to be written")
108+
}
109+
110+
func TestMalformedSnapshotJSON(t *testing.T) {
111+
withIsolatedWorkingDir(t)
112+
113+
// Given a malformed snapshot file exists
114+
require.NoError(t, os.MkdirAll("__toolsnaps__", 0700))
115+
require.NoError(t, os.WriteFile(filepath.Join("__toolsnaps__", "dummy.snap"), []byte(`not-json`), 0600))
116+
tool := dummyTool{"foo", 42}
117+
118+
// When we test the snapshot
119+
err := Test("dummy", tool)
120+
121+
// Then it should error about malformed snapshot JSON
122+
require.Error(t, err)
123+
assert.Contains(t, err.Error(), "failed to parse snapshot JSON for dummy", "expected error about malformed snapshot JSON")
124+
}

pkg/github/__toolsnaps__/get_me.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"annotations": {
3+
"title": "Get my user profile",
4+
"readOnlyHint": true
5+
},
6+
"description": "Get details of the authenticated GitHub user. Use this when a request includes \"me\", \"my\". The output will not change unless the user changes their profile, so only call this once.",
7+
"inputSchema": {
8+
"properties": {
9+
"reason": {
10+
"description": "Optional: the reason for requesting the user information",
11+
"type": "string"
12+
}
13+
},
14+
"type": "object"
15+
},
16+
"name": "get_me"
17+
}

0 commit comments

Comments
 (0)