-
Notifications
You must be signed in to change notification settings - Fork 1.3k
fix: resolve schema against runtime metadata in plugin builds; gate cache overlay by version #1764
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package registry | ||
|
|
||
| import ( | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
|
|
||
| "github.com/larksuite/cli/internal/apicatalog" | ||
| ) | ||
|
|
||
| // swapEmbeddedMeta replaces the compiled-in metadata bytes for one test and | ||
| // restores them (with a full state reset) on cleanup. | ||
| func swapEmbeddedMeta(t *testing.T, data []byte) { | ||
| t.Helper() | ||
| resetInit() | ||
| orig := embeddedMetaJSON | ||
| embeddedMetaJSON = data | ||
| t.Cleanup(func() { | ||
| waitBackgroundRefresh() | ||
| embeddedMetaJSON = orig | ||
| resetInit() | ||
| }) | ||
| } | ||
|
|
||
| func TestSchemaCatalog_EmbeddedWhenCompiledIn(t *testing.T) { | ||
| swapEmbeddedMeta(t, testCacheJSON("embedded_svc")) | ||
| t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) | ||
| t.Setenv("LARKSUITE_CLI_REMOTE_META", "off") | ||
|
|
||
| c := SchemaCatalog() | ||
|
|
||
| if c.Source() != apicatalog.SourceEmbedded { | ||
| t.Fatalf("Source = %q, want %q", c.Source(), apicatalog.SourceEmbedded) | ||
| } | ||
| if _, ok := c.Service("embedded_svc"); !ok { | ||
| t.Fatal("expected embedded_svc from embedded metadata") | ||
| } | ||
| } | ||
|
|
||
| // TestSchemaCatalog_FallsBackToRuntimeWhenNoEmbedded simulates a binary built | ||
| // from the bare Go module (plugin builds): only the empty meta_data_default.json | ||
| // stub is compiled in, so SchemaCatalog must serve the merged runtime view that | ||
| // Init seeds via sync fetch. | ||
| func TestSchemaCatalog_FallsBackToRuntimeWhenNoEmbedded(t *testing.T) { | ||
| swapEmbeddedMeta(t, embeddedMetaDataDefaultJSON) | ||
| t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) | ||
| t.Setenv("LARKSUITE_CLI_REMOTE_META", "on") | ||
|
|
||
| ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
| w.WriteHeader(200) | ||
| w.Write(testEnvelopeJSON("remote_svc")) | ||
| })) | ||
| defer ts.Close() | ||
| testMetaURL = ts.URL | ||
|
|
||
| c := SchemaCatalog() | ||
|
|
||
| if c.Source() != apicatalog.SourceRuntime { | ||
| t.Fatalf("Source = %q, want %q", c.Source(), apicatalog.SourceRuntime) | ||
| } | ||
| if _, ok := c.Service("remote_svc"); !ok { | ||
| t.Fatal("expected remote_svc from runtime fallback") | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,102 @@ | ||
| // Copyright (c) 2026 Lark Technologies Pte. Ltd. | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| package registry | ||
|
|
||
| import ( | ||
| "encoding/json" | ||
| "os" | ||
| "path/filepath" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/larksuite/cli/internal/core" | ||
| "github.com/larksuite/cli/internal/meta" | ||
| ) | ||
|
|
||
| // seedCache writes a cache file + cache meta for one service whose Title is | ||
| // marker, tagged with the given top-level data version and brand. | ||
| func seedCache(t *testing.T, dir, name, marker, version, brand string) { | ||
| t.Helper() | ||
| cDir := filepath.Join(dir, "cache") | ||
| if err := os.MkdirAll(cDir, 0700); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| reg := MergedRegistry{ | ||
| Version: version, | ||
| Services: []meta.Service{{Name: name, Version: "cache", Title: marker}}, | ||
| } | ||
| data, _ := json.Marshal(reg) | ||
| if err := os.WriteFile(filepath.Join(cDir, "remote_meta.json"), data, 0644); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| cm := CacheMeta{LastCheckAt: time.Now().Unix(), Version: version, Brand: brand} | ||
| mData, _ := json.Marshal(cm) | ||
| if err := os.WriteFile(filepath.Join(cDir, "remote_meta.meta.json"), mData, 0644); err != nil { | ||
| t.Fatal(err) | ||
| } | ||
| } | ||
|
|
||
| // initWithCache runs a fresh feishu-brand init with remote on, a high TTL and a | ||
| // recent LastCheckAt (so no refresh fires), embedded meta at embeddedVer and a | ||
| // pre-seeded cache at cacheVer — the overlay version gate is the only variable. | ||
| func initWithCache(t *testing.T, embeddedVer, cacheVer string) { | ||
| t.Helper() | ||
| embedded, _ := json.Marshal(MergedRegistry{ | ||
| Version: embeddedVer, | ||
| Services: []meta.Service{{Name: "svc", Version: "embedded", Title: "EMBEDDED"}}, | ||
| }) | ||
| swapEmbeddedMeta(t, embedded) | ||
| tmp := t.TempDir() | ||
| t.Setenv("LARKSUITE_CLI_CONFIG_DIR", tmp) | ||
| t.Setenv("LARKSUITE_CLI_REMOTE_META", "on") | ||
| t.Setenv("LARKSUITE_CLI_META_TTL", "3600") | ||
| seedCache(t, tmp, "svc", "CACHE", cacheVer, "feishu") | ||
| InitWithBrand(core.BrandFeishu) | ||
| } | ||
|
|
||
| func titleOf(t *testing.T, name string) string { | ||
| t.Helper() | ||
| svc, ok := ServiceTyped(name) | ||
| if !ok { | ||
| t.Fatalf("service %q not loaded", name) | ||
| } | ||
| return svc.Title | ||
| } | ||
|
|
||
| func TestOverlayGate_EqualVersion_UsesEmbedded(t *testing.T) { | ||
| initWithCache(t, "1.0.0", "1.0.0") | ||
| if got := titleOf(t, "svc"); got != "EMBEDDED" { | ||
| t.Errorf("equal version: got %q, want EMBEDDED (cache must not overlay)", got) | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayGate_OlderCache_UsesEmbedded(t *testing.T) { | ||
| initWithCache(t, "2.0.0", "1.0.0") | ||
| if got := titleOf(t, "svc"); got != "EMBEDDED" { | ||
| t.Errorf("older cache: got %q, want EMBEDDED", got) | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayGate_NewerCache_OverlaysCache(t *testing.T) { | ||
| initWithCache(t, "1.0.0", "2.0.0") | ||
| if got := titleOf(t, "svc"); got != "CACHE" { | ||
| t.Errorf("newer cache: got %q, want CACHE", got) | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayGate_UnparseableCacheVersion_UsesEmbedded(t *testing.T) { | ||
| initWithCache(t, "1.0.0", "not-a-semver") | ||
| if got := titleOf(t, "svc"); got != "EMBEDDED" { | ||
| t.Errorf("unparseable cache version: got %q, want EMBEDDED", got) | ||
| } | ||
| } | ||
|
|
||
| func TestOverlayGate_StubEmbedded_OverlaysRealCache(t *testing.T) { | ||
| // The bare-module stub baseline is "0.0.0"; a real cache version must win so | ||
| // plugin builds without compiled meta_data.json still get remote data. | ||
| initWithCache(t, "0.0.0", "1.0.0") | ||
| if got := titleOf(t, "svc"); got != "CACHE" { | ||
| t.Errorf("stub-embedded baseline: got %q, want CACHE", got) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.