Files
llmgw/internal/vscodeext/vsix_test.go
T
Ilker ManapandClaude Opus 5.5 38dfe268c9 VS Code gets the gateway's models from an extension the gateway serves
llmgw's own extension for VS Code, plain JavaScript with no dependencies,
packed into a .vsix by the binary at /setup/vscode/llmgw.vsix with the
serving gateway's address in it, so it needs no build, no Node and no
marketplace. It registers llmgw as a language-model provider: the chat's
model picker, agent mode and any extension that asks VS Code for a model
get every chat model the key may use, sized and with tool and vision
capabilities as the catalog says, streamed, tool calls both ways.

VS Code is a tool on the account page's Connect your editor: a key, the
install command, and an Open in VS Code link that hands the key to the
extension's secret storage. The extension's Sign In opens the page with a
state of its own; a link without it is confirmed, the gateway named, since
it decides where code is sent.

Tested in VS Code 1.138 against the PoC: 27 models listed, an answer from
qwen2.5-0.5b-instruct, and qwen3-coder-30b calling a tool and using its
result (internal/vscodeext/e2e).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:39:50 +02:00

151 lines
3.9 KiB
Go

package vscodeext
import (
"archive/zip"
"bytes"
"encoding/json"
"encoding/xml"
"io"
"slices"
"testing"
)
func TestVersion(t *testing.T) {
for in, want := range map[string]string{
"v0.14.9": "0.14.9",
"v0.14.9-11-g4eb49d0-dev": "0.14.9",
"1.2.3": "1.2.3",
"dev": "0.0.0",
"": "0.0.0",
} {
if got := Version(in); got != want {
t.Errorf("Version(%q) = %q, want %q", in, got, want)
}
}
}
func unzip(t *testing.T, b []byte) map[string][]byte {
t.Helper()
zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
if err != nil {
t.Fatal(err)
}
out := map[string][]byte{}
for _, f := range zr.File {
rc, err := f.Open()
if err != nil {
t.Fatal(err)
}
body, _ := io.ReadAll(rc)
rc.Close()
out[f.Name] = body
}
return out
}
func TestBuild(t *testing.T) {
b, err := Build("v0.15.2-3-gabc", "https://llm.example.com")
if err != nil {
t.Fatal(err)
}
got := unzip(t, b)
for _, name := range []string{
"[Content_Types].xml", "extension.vsixmanifest",
"extension/package.json", "extension/extension.js", "extension/lib.js", "extension/README.md",
} {
if _, ok := got[name]; !ok {
t.Errorf("%s missing from the package", name)
}
}
for name := range got {
if bytes.HasSuffix([]byte(name), []byte("_test.js")) {
t.Errorf("%s is a test and should stay out of the package", name)
}
}
var pkg map[string]any
if err := json.Unmarshal(got["extension/package.json"], &pkg); err != nil {
t.Fatal(err)
}
if pkg["version"] != "0.15.2" {
t.Errorf("version %v, want 0.15.2", pkg["version"])
}
setting, _ := dig(pkg, "contributes", "configuration", "properties", "llmgw.gatewayUrl")
if setting["default"] != "https://llm.example.com" {
t.Errorf("gatewayUrl default %v, want the serving gateway", setting["default"])
}
var m struct {
Metadata struct {
Identity struct {
ID string `xml:"Id,attr"`
Version string `xml:"Version,attr"`
Publisher string `xml:"Publisher,attr"`
}
Properties struct {
Property []struct {
ID string `xml:"Id,attr"`
Value string `xml:"Value,attr"`
}
}
}
Assets struct {
Asset []struct {
Type string `xml:"Type,attr"`
Path string `xml:"Path,attr"`
}
}
}
if err := xml.Unmarshal(got["extension.vsixmanifest"], &m); err != nil {
t.Fatalf("extension.vsixmanifest: %v", err)
}
id := m.Metadata.Identity
if id.Publisher+"."+id.ID != ID || id.Version != "0.15.2" {
t.Errorf("identity %s.%s@%s, want %s@0.15.2", id.Publisher, id.ID, id.Version, ID)
}
props := map[string]string{}
for _, p := range m.Metadata.Properties.Property {
props[p.ID] = p.Value
}
if props["Microsoft.VisualStudio.Code.Engine"] != "^1.104.0" {
t.Errorf("engine %q", props["Microsoft.VisualStudio.Code.Engine"])
}
if props["Microsoft.VisualStudio.Code.ExtensionKind"] != "ui,workspace" {
t.Errorf("extension kind %q", props["Microsoft.VisualStudio.Code.ExtensionKind"])
}
if !slices.ContainsFunc(m.Assets.Asset, func(a struct {
Type string `xml:"Type,attr"`
Path string `xml:"Path,attr"`
}) bool {
return a.Type == "Microsoft.VisualStudio.Code.Manifest" && a.Path == "extension/package.json"
}) {
t.Error("no manifest asset pointing at extension/package.json")
}
}
func TestBuildIsReproducible(t *testing.T) {
a, err := Build("v1.0.0", "https://a")
if err != nil {
t.Fatal(err)
}
b, _ := Build("v1.0.0", "https://a")
if !bytes.Equal(a, b) {
t.Error("the same inputs made different packages")
}
}
// The address goes into JSON and XML; neither may let it out of its string.
func TestBuildEscapesGateway(t *testing.T) {
b, err := Build("v1.0.0", `https://x"},"main":"evil.js`)
if err != nil {
t.Fatal(err)
}
var pkg map[string]any
if err := json.Unmarshal(unzip(t, b)["extension/package.json"], &pkg); err != nil {
t.Fatal(err)
}
if pkg["main"] != "./extension.js" {
t.Errorf("main = %v: the gateway address escaped its string", pkg["main"])
}
}