mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 17:45:15 +08:00
Compare commits
2 Commits
feature/la
...
fix/skill-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e01132227d | ||
|
|
aca356ece6 |
@@ -10,6 +10,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestScopeIncludesChangedSkillAndRelatedDomain(t *testing.T) {
|
||||
@@ -51,7 +52,7 @@ func TestFileAtRevisionMissingClassifier(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChangedFilesIncludingWorktree(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -83,7 +84,7 @@ func TestChangedFilesIncludingWorktree(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChangedFilesHandlesWhitespacePaths(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -139,3 +140,22 @@ func gitOutput(t *testing.T, repo string, args ...string) string {
|
||||
}
|
||||
return string(out[:len(out)-1])
|
||||
}
|
||||
|
||||
// newGitTestRepo returns a temp dir for a test-created git repo. Git tooling
|
||||
// on this machine (trace2 hooks, etc.) can asynchronously write into a
|
||||
// repo's .git/ shortly after a git command runs, racing with t.TempDir's
|
||||
// automatic RemoveAll cleanup. Removing the tree ourselves first (retrying
|
||||
// past that transient window) makes the later t.TempDir cleanup a no-op.
|
||||
func newGitTestRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
repo := t.TempDir()
|
||||
t.Cleanup(func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := os.RemoveAll(repo); err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
return repo
|
||||
}
|
||||
|
||||
@@ -10,10 +10,11 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestCollectScansOnlyCurrentContributionAndMetadata(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -60,7 +61,7 @@ api_`+`key = "example-public-key"
|
||||
}
|
||||
|
||||
func TestCollectScansOnlyChangedLinesInChangedFiles(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -92,7 +93,7 @@ func TestCollectScansOnlyChangedLinesInChangedFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectSemanticCandidatesStoreSanitizedReviewText(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -608,7 +609,7 @@ func TestCollectIgnoresDeletedPrivateKeyLine(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectSkipsOnlyKnownQualityGateFixtureFiles(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -699,7 +700,7 @@ func TestCollectScansAddedLinesInSpecialPathNames(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestCollectScansBranchNameAsWarning(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
metadataPath := filepath.Join(repo, "pr-metadata.json")
|
||||
writeFile(t, metadataPath, `{"branch":"bot/public-doc-update"}`)
|
||||
got, err := Collect(context.Background(), Options{
|
||||
@@ -799,7 +800,7 @@ func TestAppendUniqueFindingsDeduplicatesByRuleFileLineAndSource(t *testing.T) {
|
||||
|
||||
func newGitRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -840,7 +841,7 @@ func requireFinding(t *testing.T, got []Finding, file, rule string) {
|
||||
}
|
||||
|
||||
func TestCollectRequiresValidMetadataJSON(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
metadataPath := filepath.Join(repo, "pr-metadata.json")
|
||||
writeFile(t, metadataPath, `{"title":`)
|
||||
|
||||
@@ -874,6 +875,25 @@ func runGitOutput(t *testing.T, repo string, args ...string) []byte {
|
||||
return out
|
||||
}
|
||||
|
||||
// newGitTestRepo returns a temp dir for a test-created git repo. Git tooling
|
||||
// on this machine (trace2 hooks, etc.) can asynchronously write into a
|
||||
// repo's .git/ shortly after a git command runs, racing with t.TempDir's
|
||||
// automatic RemoveAll cleanup. Removing the tree ourselves first (retrying
|
||||
// past that transient window) makes the later t.TempDir cleanup a no-op.
|
||||
func newGitTestRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
repo := t.TempDir()
|
||||
t.Cleanup(func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := os.RemoveAll(repo); err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
return repo
|
||||
}
|
||||
|
||||
func writeFile(t *testing.T, path, data string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
qdiff "github.com/larksuite/cli/internal/qualitygate/diff"
|
||||
"github.com/larksuite/cli/internal/qualitygate/manifest"
|
||||
@@ -69,7 +70,7 @@ func TestReferenceCommandSurfaceNormalizesShortcutDomain(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunRequiresCommandIndexToCoverManifest(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
manifestPath := filepath.Join(repo, "command-manifest.json")
|
||||
indexPath := filepath.Join(repo, "command-index.json")
|
||||
m := manifest.Manifest{SchemaVersion: 1, Commands: []manifest.Command{{
|
||||
@@ -104,7 +105,7 @@ func TestRunRequiresCommandIndexToCoverManifest(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRunReadsManifestFilesAndAcceptsServiceReferences(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -190,7 +191,7 @@ description: Manage Drive comments with service command references.
|
||||
}
|
||||
|
||||
func TestRunCollectsPublicContentFindingsIntoDiagnosticsAndFacts(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -283,7 +284,7 @@ func TestRunCollectsPublicContentFindingsIntoDiagnosticsAndFacts(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadBaseReferenceManifestReadsCommandGolden(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -323,7 +324,7 @@ func TestLoadBaseReferenceManifestReadsCommandGolden(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadBaseReferenceManifestReadsCommandIndexGolden(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -364,7 +365,7 @@ func TestLoadBaseReferenceManifestReadsCommandIndexGolden(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadBaseReferenceManifestRejectsEmptyGolden(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -384,7 +385,7 @@ func TestLoadBaseReferenceManifestRejectsEmptyGolden(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadBaseReferenceManifestRejectsInvalidGoldenKind(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
repo := newGitTestRepo(t)
|
||||
runGit(t, repo, "init")
|
||||
runGit(t, repo, "config", "user.email", "test@example.com")
|
||||
runGit(t, repo, "config", "user.name", "Test User")
|
||||
@@ -606,3 +607,22 @@ func runGit(t *testing.T, repo string, args ...string) {
|
||||
t.Fatalf("git %v failed: %v\n%s", args, err, out)
|
||||
}
|
||||
}
|
||||
|
||||
// newGitTestRepo returns a temp dir for a test-created git repo. Git tooling
|
||||
// on this machine (trace2 hooks, etc.) can asynchronously write into a
|
||||
// repo's .git/ shortly after a git command runs, racing with t.TempDir's
|
||||
// automatic RemoveAll cleanup. Removing the tree ourselves first (retrying
|
||||
// past that transient window) makes the later t.TempDir cleanup a no-op.
|
||||
func newGitTestRepo(t *testing.T) string {
|
||||
t.Helper()
|
||||
repo := t.TempDir()
|
||||
t.Cleanup(func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
if err := os.RemoveAll(repo); err == nil {
|
||||
return
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
})
|
||||
return repo
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ func SyncSkills(opts SyncOptions) *SyncResult {
|
||||
UpdatedSkills: plan.ToUpdate,
|
||||
AddedOfficialSkills: plan.Added,
|
||||
SkippedDeletedSkills: plan.SkippedDeleted,
|
||||
UpdatedAt: opts.Now().UTC().Format(time.RFC3339),
|
||||
UpdatedAt: opts.Now().Format(time.RFC3339),
|
||||
}
|
||||
if err := WriteState(state); err != nil {
|
||||
result.Action = "failed"
|
||||
@@ -428,7 +428,7 @@ func fallbackFullInstall(opts SyncOptions, reason string, official []string) *Sy
|
||||
UpdatedSkills: official,
|
||||
AddedOfficialSkills: official,
|
||||
SkippedDeletedSkills: []string{},
|
||||
UpdatedAt: opts.Now().UTC().Format(time.RFC3339),
|
||||
UpdatedAt: opts.Now().Format(time.RFC3339),
|
||||
}
|
||||
if writeErr := WriteState(state); writeErr != nil {
|
||||
return &SyncResult{
|
||||
|
||||
@@ -853,3 +853,65 @@ func TestSyncSkills_FallbackBreaksDegradationLoop(t *testing.T) {
|
||||
t.Fatalf("second sync: installedAll = %d, want 0 (incremental, not fallback)", runner2.installedAll)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSkills_WritesLocalTimezoneUpdatedAt(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
|
||||
|
||||
runner := &fakeSkillsRunner{
|
||||
officialIndexOut: officialSkillsIndexOutput("lark-calendar", "lark-mail", "lark-new"),
|
||||
officialOut: officialSkillsOutput("lark-calendar", "lark-mail", "lark-new"),
|
||||
globalJSONOut: globalSkillsJSONOutput("lark-calendar"),
|
||||
globalOut: globalSkillsOutput("lark-mail"),
|
||||
}
|
||||
localZone := time.FixedZone("UTC+8", 8*60*60)
|
||||
result := SyncSkills(SyncOptions{
|
||||
Version: "1.0.33",
|
||||
Runner: runner,
|
||||
Now: func() time.Time { return time.Date(2026, 5, 18, 12, 0, 0, 0, localZone) },
|
||||
})
|
||||
if result.Err != nil {
|
||||
t.Fatalf("SyncSkills() err = %v, want nil", result.Err)
|
||||
}
|
||||
|
||||
state, readable, err := ReadState()
|
||||
if err != nil || !readable {
|
||||
t.Fatalf("ReadState() = (_, %v, %v), want readable", readable, err)
|
||||
}
|
||||
want := "2026-05-18T12:00:00+08:00"
|
||||
if state.UpdatedAt != want {
|
||||
t.Fatalf("state.UpdatedAt = %q, want %q (local timezone offset, not UTC Z-suffix)", state.UpdatedAt, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncSkills_FallbackWritesLocalTimezoneUpdatedAt(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", dir)
|
||||
|
||||
runner := &fakeSkillsRunner{
|
||||
officialIndexOut: officialSkillsIndexOutput("lark-calendar", "lark-mail"),
|
||||
officialOut: officialSkillsOutput("lark-calendar", "lark-mail"),
|
||||
globalJSONOut: globalSkillsJSONOutput("lark-calendar", "lark-mail"),
|
||||
globalOut: globalSkillsOutput("lark-calendar", "lark-mail"),
|
||||
installErr: fmt.Errorf("incremental boom"),
|
||||
installAllErr: nil,
|
||||
}
|
||||
localZone := time.FixedZone("UTC+8", 8*60*60)
|
||||
result := SyncSkills(SyncOptions{
|
||||
Version: "1.0.33",
|
||||
Runner: runner,
|
||||
Now: func() time.Time { return time.Date(2026, 5, 18, 12, 0, 0, 0, localZone) },
|
||||
})
|
||||
if result.Action != "fallback_synced" {
|
||||
t.Fatalf("SyncSkills() action = %q, want fallback_synced", result.Action)
|
||||
}
|
||||
|
||||
state, readable, err := ReadState()
|
||||
if err != nil || !readable {
|
||||
t.Fatalf("ReadState() = (_, %v, %v), want readable", readable, err)
|
||||
}
|
||||
want := "2026-05-18T12:00:00+08:00"
|
||||
if state.UpdatedAt != want {
|
||||
t.Fatalf("state.UpdatedAt = %q, want %q (local timezone offset, not UTC Z-suffix)", state.UpdatedAt, want)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user