mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
Add semi-plain text (simple) format for OKR content I/O, and a new `+patch` shortcut for incremental updates to objectives and key results.
564 lines
18 KiB
Go
564 lines
18 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package okr
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
clie2e "github.com/larksuite/cli/tests/cli_e2e"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
// --- Dry-run E2E tests for +batch-create, +reorder, +weight ---
|
|
|
|
// TestOKR_BatchCreateDryRun validates +batch-create dry-run output contains expected API paths.
|
|
func TestOKR_BatchCreateDryRun(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+batch-create",
|
|
"--cycle-id", "123456",
|
|
"--input", `[{"text":"Objective 1","krs":[{"text":"KR 1"}]}]`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/cycles/123456/objectives"), "dry-run should contain objective API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/objectives/"), "dry-run should contain KR API path prefix, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "123456"), "dry-run should contain cycle-id, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_BatchCreateDryRun_WithUserIDType validates +batch-create dry-run with --user-id-type.
|
|
func TestOKR_BatchCreateDryRun_WithUserIDType(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+batch-create",
|
|
"--cycle-id", "123456",
|
|
"--input", `[{"text":"Objective 1","krs":[{"text":"KR 1"}]}]`,
|
|
"--user-id-type", "user_id",
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "user_id"), "dry-run should contain user-id-type, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_ReorderDryRun validates +reorder dry-run output contains expected API paths.
|
|
func TestOKR_ReorderDryRun(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+reorder",
|
|
"--cycle-id", "123456",
|
|
"--level", "objective",
|
|
"--ops", `[{"id":"obj_1","position":2},{"id":"obj_2","position":1}]`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/cycles/123456/objectives"), "dry-run should contain objective API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "123456"), "dry-run should contain cycle-id, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_ReorderDryRun_KR validates +reorder dry-run with --level=key-result.
|
|
func TestOKR_ReorderDryRun_KR(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+reorder",
|
|
"--cycle-id", "123456",
|
|
"--objective-id", "789",
|
|
"--level", "key-result",
|
|
"--ops", `[{"id":"1001","position":2},{"id":"1002","position":1}]`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/objectives/789/key_results"), "dry-run should contain KR API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "789"), "dry-run should contain objective-id, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_WeightDryRun validates +weight dry-run output contains expected API paths.
|
|
func TestOKR_WeightDryRun(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+weight",
|
|
"--cycle-id", "123456",
|
|
"--level", "objective",
|
|
"--weights", `[{"id":"obj_1","weight":0.6},{"id":"obj_2","weight":0.4}]`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/cycles/123456/objectives"), "dry-run should contain objective API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "123456"), "dry-run should contain cycle-id, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_WeightDryRun_KR validates +weight dry-run with --level=key-result.
|
|
func TestOKR_WeightDryRun_KR(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+weight",
|
|
"--cycle-id", "123456",
|
|
"--objective-id", "789",
|
|
"--level", "key-result",
|
|
"--weights", `[{"id":"1001","weight":0.5},{"id":"1002","weight":0.5}]`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/objectives/789/key_results"), "dry-run should contain KR API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "789"), "dry-run should contain objective-id, got: %s", output)
|
|
}
|
|
|
|
// --- Dry-run E2E tests for +patch ---
|
|
|
|
// TestOKR_PatchDryRun_Objective validates +patch dry-run for objective with content.
|
|
func TestOKR_PatchDryRun_Objective(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+patch",
|
|
"--level", "objective",
|
|
"--target-id", "123",
|
|
"--content", `{"text":"updated content","mention":["ou_123"]}`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "PATCH"), "dry-run should contain PATCH method, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/objectives/123"), "dry-run should contain objective API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "content=true"), "dry-run should show content patch, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_PatchDryRun_Objective_AllFields validates +patch dry-run for objective with all fields.
|
|
func TestOKR_PatchDryRun_Objective_AllFields(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+patch",
|
|
"--level", "objective",
|
|
"--target-id", "456",
|
|
"--style", "simple",
|
|
"--content", `{"text":"new content"}`,
|
|
"--notes", `{"text":"new notes"}`,
|
|
"--score", "0.7",
|
|
"--deadline", "1735776000000",
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/objectives/456"), "dry-run should contain objective API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "content=true"), "dry-run should show content patch, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "notes=true"), "dry-run should show notes patch, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "score=true"), "dry-run should show score patch, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "deadline=true"), "dry-run should show deadline patch, got: %s", output)
|
|
assert.True(t, strings.Contains(output, `"user_id_type": "open_id"`), "dry-run should contain user_id_type param, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_PatchDryRun_KeyResult validates +patch dry-run for key result.
|
|
func TestOKR_PatchDryRun_KeyResult(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+patch",
|
|
"--level", "key-result",
|
|
"--target-id", "789",
|
|
"--score", "0.5",
|
|
"--user-id-type", "user_id",
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "PATCH"), "dry-run should contain PATCH method, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/key_results/789"), "dry-run should contain key_result API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "score=true"), "dry-run should show score patch, got: %s", output)
|
|
assert.True(t, strings.Contains(output, `"user_id_type": "user_id"`), "dry-run should contain user_id_type param, got: %s", output)
|
|
}
|
|
|
|
// TestOKR_PatchDryRun_KeyResult_RichText validates +patch dry-run for key result with richtext style.
|
|
func TestOKR_PatchDryRun_KeyResult_RichText(t *testing.T) {
|
|
setDryRunConfigEnv(t)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
t.Cleanup(cancel)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+patch",
|
|
"--level", "key-result",
|
|
"--target-id", "101",
|
|
"--style", "richtext",
|
|
"--content", `{"blocks":[{"block_element_type":"paragraph","paragraph":{"elements":[{"paragraph_element_type":"textRun","text_run":{"text":"updated"}}]}}]}`,
|
|
"--dry-run",
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
output := result.Stdout
|
|
assert.True(t, strings.Contains(output, "/open-apis/okr/v2/key_results/101"), "dry-run should contain key_result API path, got: %s", output)
|
|
assert.True(t, strings.Contains(output, "content=true"), "dry-run should show content patch, got: %s", output)
|
|
}
|
|
|
|
// --- Live E2E tests (require user token, skip otherwise) ---
|
|
|
|
// getTestCycleID returns the test cycle ID from env var, or skips the test.
|
|
func getTestCycleID(t *testing.T) string {
|
|
t.Helper()
|
|
cycleID := os.Getenv("OKR_TEST_CYCLE_ID")
|
|
if cycleID == "" {
|
|
t.Skip("OKR_TEST_CYCLE_ID not set; set to a valid cycle ID for live E2E tests")
|
|
}
|
|
return cycleID
|
|
}
|
|
|
|
// liveTestCreated tracks resources created during a live test for cleanup.
|
|
type liveTestCreated struct {
|
|
ObjectiveID string
|
|
KRIDs []string
|
|
}
|
|
|
|
// createTestObjectives creates test objectives using +batch-create and returns the created IDs.
|
|
func createTestObjectives(t *testing.T, ctx context.Context, cycleID string, suffix string) []liveTestCreated {
|
|
t.Helper()
|
|
|
|
input := []map[string]interface{}{
|
|
{
|
|
"text": fmt.Sprintf("E2E Test Objective A %s", suffix),
|
|
"krs": []map[string]interface{}{
|
|
{"text": fmt.Sprintf("E2E Test KR A1 %s", suffix)},
|
|
{"text": fmt.Sprintf("E2E Test KR A2 %s", suffix)},
|
|
},
|
|
},
|
|
{
|
|
"text": fmt.Sprintf("E2E Test Objective B %s", suffix),
|
|
"krs": []map[string]interface{}{
|
|
{"text": fmt.Sprintf("E2E Test KR B1 %s", suffix)},
|
|
},
|
|
},
|
|
}
|
|
inputJSON, _ := json.Marshal(input)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+batch-create",
|
|
"--cycle-id", cycleID,
|
|
"--input", string(inputJSON),
|
|
},
|
|
})
|
|
require.NoError(t, err, "failed to create test objectives")
|
|
result.AssertExitCode(t, 0)
|
|
|
|
var created []liveTestCreated
|
|
createdArr := gjson.Get(result.Stdout, "data.created").Array()
|
|
for _, obj := range createdArr {
|
|
objectiveID := obj.Get("objective_id").String()
|
|
var krIDs []string
|
|
for _, kr := range obj.Get("krs").Array() {
|
|
krIDs = append(krIDs, kr.String())
|
|
}
|
|
created = append(created, liveTestCreated{
|
|
ObjectiveID: objectiveID,
|
|
KRIDs: krIDs,
|
|
})
|
|
}
|
|
|
|
require.Len(t, created, 2, "expected 2 objectives created")
|
|
require.Len(t, created[0].KRIDs, 2, "expected 2 KRs for first objective")
|
|
require.Len(t, created[1].KRIDs, 1, "expected 1 KR for second objective")
|
|
require.NotEmpty(t, created[0].ObjectiveID, "objective_id should not be empty")
|
|
require.NotEmpty(t, created[0].KRIDs[0], "kr_id should not be empty")
|
|
|
|
return created
|
|
}
|
|
|
|
// cleanupLiveTest deletes KRs first, then objectives, using the raw API service commands.
|
|
func cleanupLiveTest(t *testing.T, created []liveTestCreated) {
|
|
t.Helper()
|
|
cleanupCtx, cleanupCancel := clie2e.CleanupContext()
|
|
defer cleanupCancel()
|
|
|
|
// Delete in reverse order: KRs first, then objectives
|
|
for i := len(created) - 1; i >= 0; i-- {
|
|
obj := created[i]
|
|
// Delete KRs first (reverse order)
|
|
for j := len(obj.KRIDs) - 1; j >= 0; j-- {
|
|
krID := obj.KRIDs[j]
|
|
result, err := clie2e.RunCmd(cleanupCtx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "v2/key_results", "delete",
|
|
"--key-result-id", krID,
|
|
"--yes",
|
|
},
|
|
})
|
|
clie2e.ReportCleanupFailure(t, fmt.Sprintf("delete KR %s", krID), result, err)
|
|
select {
|
|
case <-cleanupCtx.Done():
|
|
return
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
}
|
|
// Then delete the objective
|
|
result, err := clie2e.RunCmd(cleanupCtx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "v2/objectives", "delete",
|
|
"--objective-id", obj.ObjectiveID,
|
|
"--yes",
|
|
},
|
|
})
|
|
clie2e.ReportCleanupFailure(t, fmt.Sprintf("delete objective %s", obj.ObjectiveID), result, err)
|
|
if i > 0 {
|
|
select {
|
|
case <-cleanupCtx.Done():
|
|
return
|
|
case <-time.After(200 * time.Millisecond):
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestOKR_BatchCreateLive validates +batch-create with real API calls: create, verify, cleanup.
|
|
func TestOKR_BatchCreateLive(t *testing.T) {
|
|
clie2e.SkipWithoutUserToken(t)
|
|
cycleID := getTestCycleID(t)
|
|
suffix := clie2e.GenerateSuffix()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
t.Cleanup(cancel)
|
|
|
|
// Create test objectives
|
|
created := createTestObjectives(t, ctx, cycleID, suffix)
|
|
|
|
// Register cleanup immediately after create to ensure resources are cleaned up even if later code fails
|
|
t.Cleanup(func() {
|
|
cleanupLiveTest(t, created)
|
|
})
|
|
|
|
// Verify: call +cycle-detail to confirm objectives exist
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+cycle-detail",
|
|
"--cycle-id", cycleID,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
objectives := gjson.Get(result.Stdout, "data.objectives").Array()
|
|
foundCount := 0
|
|
for _, obj := range objectives {
|
|
objID := obj.Get("id").String()
|
|
for _, c := range created {
|
|
if objID == c.ObjectiveID {
|
|
foundCount++
|
|
// Verify KRs exist under this objective
|
|
krs := obj.Get("key_results").Array()
|
|
krIDs := make(map[string]bool)
|
|
for _, kr := range krs {
|
|
krIDs[kr.Get("id").String()] = true
|
|
}
|
|
for _, expectedKR := range c.KRIDs {
|
|
assert.True(t, krIDs[expectedKR], "expected KR %s to exist under objective %s", expectedKR, objID)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert.Equal(t, len(created), foundCount, "all created objectives should be found in cycle detail")
|
|
}
|
|
|
|
// TestOKR_ReorderLive validates +reorder with real API calls: create, reorder, verify, cleanup.
|
|
func TestOKR_ReorderLive(t *testing.T) {
|
|
clie2e.SkipWithoutUserToken(t)
|
|
cycleID := getTestCycleID(t)
|
|
suffix := clie2e.GenerateSuffix()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
t.Cleanup(cancel)
|
|
|
|
// Create test objectives (A, then B)
|
|
created := createTestObjectives(t, ctx, cycleID, suffix)
|
|
|
|
// Register cleanup immediately after create to ensure resources are cleaned up even if later code fails
|
|
t.Cleanup(func() {
|
|
cleanupLiveTest(t, created)
|
|
})
|
|
|
|
objA := created[0].ObjectiveID
|
|
objB := created[1].ObjectiveID
|
|
|
|
// Reorder: swap positions (B at position 1, A at position 2)
|
|
ops := []map[string]interface{}{
|
|
{"id": objB, "position": 1},
|
|
{"id": objA, "position": 2},
|
|
}
|
|
opsJSON, _ := json.Marshal(ops)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+reorder",
|
|
"--cycle-id", cycleID,
|
|
"--level", "objective",
|
|
"--ops", string(opsJSON),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
// Verify order via +cycle-detail
|
|
result, err = clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+cycle-detail",
|
|
"--cycle-id", cycleID,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
objectives := gjson.Get(result.Stdout, "data.objectives").Array()
|
|
var foundIDs []string
|
|
for _, obj := range objectives {
|
|
objID := obj.Get("id").String()
|
|
if objID == objA || objID == objB {
|
|
foundIDs = append(foundIDs, objID)
|
|
}
|
|
}
|
|
|
|
require.Len(t, foundIDs, 2, "should find both test objectives")
|
|
assert.Equal(t, objB, foundIDs[0], "after reorder, objective B should be first")
|
|
assert.Equal(t, objA, foundIDs[1], "after reorder, objective A should be second")
|
|
}
|
|
|
|
// TestOKR_WeightLive validates +weight with real API calls: create, set weights, verify sum=1.0, cleanup.
|
|
func TestOKR_WeightLive(t *testing.T) {
|
|
clie2e.SkipWithoutUserToken(t)
|
|
cycleID := getTestCycleID(t)
|
|
suffix := clie2e.GenerateSuffix()
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
|
t.Cleanup(cancel)
|
|
|
|
// Create test objectives
|
|
created := createTestObjectives(t, ctx, cycleID, suffix)
|
|
|
|
// Register cleanup immediately after create to ensure resources are cleaned up even if later code fails
|
|
t.Cleanup(func() {
|
|
cleanupLiveTest(t, created)
|
|
})
|
|
|
|
objA := created[0].ObjectiveID
|
|
objB := created[1].ObjectiveID
|
|
|
|
// Set weights: A=0.6, B=0.4 (sum=1.0)
|
|
weights := []map[string]interface{}{
|
|
{"id": objA, "weight": 0.6},
|
|
{"id": objB, "weight": 0.4},
|
|
}
|
|
weightsJSON, _ := json.Marshal(weights)
|
|
|
|
result, err := clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+weight",
|
|
"--cycle-id", cycleID,
|
|
"--level", "objective",
|
|
"--weights", string(weightsJSON),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
// Verify weights via +cycle-detail
|
|
result, err = clie2e.RunCmd(ctx, clie2e.Request{
|
|
Args: []string{
|
|
"okr", "+cycle-detail",
|
|
"--cycle-id", cycleID,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
result.AssertExitCode(t, 0)
|
|
|
|
objectives := gjson.Get(result.Stdout, "data.objectives").Array()
|
|
var weightA, weightB float64
|
|
for _, obj := range objectives {
|
|
objID := obj.Get("id").String()
|
|
if objID == objA {
|
|
weightA = obj.Get("weight").Float()
|
|
} else if objID == objB {
|
|
weightB = obj.Get("weight").Float()
|
|
}
|
|
}
|
|
|
|
// Verify weights are set correctly (allowing for floating point tolerance)
|
|
assert.InDelta(t, 0.6, weightA, 0.001, "objective A weight should be 0.6")
|
|
assert.InDelta(t, 0.4, weightB, 0.001, "objective B weight should be 0.4")
|
|
|
|
// Verify sum = 1.0
|
|
sumWeights := weightA + weightB
|
|
assert.InDelta(t, 1.0, sumWeights, 0.001, "sum of weights should be 1.0, got %.6f", sumWeights)
|
|
}
|