Files
openclaw/apps/shared/OpenClawKit/Tests/OpenClawKitTests/SkillManagementTests.swift
Peter Steinberger b57e7b74e6 feat: add native Skills management parity (#105814)
* feat(apps): add native skills management parity

* chore(i18n): refresh native skills inventory

* style(apps): apply canonical Swift formatting

* style(ios): wrap skills status message

* chore(i18n): refresh native skill locales

* refactor(apps): trim native skills review surface

* chore(i18n): regenerate native app resources

* chore: keep release changelog owner-only

* fix(ios): remove dead gateway and watch wrappers
2026-07-13 00:11:11 -07:00

111 lines
4.6 KiB
Swift

import Foundation
import OpenClawProtocol
import Testing
@testable import OpenClawKit
struct SkillManagementTests {
@Test func `detail review uses exact detail version and publisher`() throws {
let data = Data(
#"{"skill":{"displayName":"Weather","summary":"Forecasts"},"latestVersion":{"version":"2.0.0"},"owner":{"handle":"molly","displayName":"Molly"}}"#
.utf8)
let fallbackData = Data(
#"{"slug":"weather","displayName":"Old Weather","summary":null,"version":"1.0.0"}"#.utf8)
let detail = try JSONDecoder().decode(ClawHubSkillDetail.self, from: data)
let fallback = try JSONDecoder().decode(ClawHubSkillSummary.self, from: fallbackData)
let review = try #require(ClawHubSkillInstallReview(
detail: detail,
fallback: fallback))
#expect(review.slug == "@molly/weather")
#expect(review.displayName == "Weather")
#expect(review.version == "2.0.0")
#expect(review.author == "Molly (@molly)")
}
@Test func `risk acknowledgement stays bound to reviewed version`() {
let matching = GatewayResponseError(
method: "skills.install",
code: "UNAVAILABLE",
message: "Review warning",
details: [
"clawhubTrustCode": AnyCodable("clawhub_risk_acknowledgement_required"),
"version": AnyCodable("2.0.0"),
"warning": AnyCodable("Automated analysis found risky behavior."),
])
let accepted = SkillManagementContract.rejection(from: matching, attemptedVersion: "2.0.0")
#expect(accepted.requiresAcknowledgement)
#expect(accepted.acknowledgeVersion == "2.0.0")
let stale = SkillManagementContract.rejection(from: matching, attemptedVersion: "1.0.0")
#expect(!stale.requiresAcknowledgement)
#expect(stale.acknowledgeVersion == nil)
#expect(stale.message.contains("different ClawHub release"))
}
@Test func `installed readback requires valid provenance and exact version`() {
let linked = Self.skill(
clawhub: ClawHubInstalledSkillLink(
status: "linked",
valid: true,
slug: "@molly/weather",
ownerHandle: "molly",
installedVersion: "2.0.0",
reason: nil))
#expect(SkillManagementContract.installed([linked], slug: "weather", version: "2.0.0"))
#expect(!SkillManagementContract.installed([linked], slug: "weather", version: "2.0.1"))
#expect(SkillManagementContract.installed([linked], slug: "weather"))
}
@Test func `owner qualified readback matches split provenance identity`() {
let linked = Self.skill(
clawhub: ClawHubInstalledSkillLink(
status: "linked",
valid: true,
slug: "weather",
ownerHandle: "molly",
installedVersion: "2.0.0",
reason: nil))
#expect(SkillManagementContract.installed([linked], slug: "@molly/weather", version: "2.0.0"))
#expect(!SkillManagementContract.installed([linked], slug: "@other/weather", version: "2.0.0"))
}
@Test func `agent filtered skills need setup instead of reporting ready`() {
let blocked = Self.skill(clawhub: nil, blockedByAgentFilter: true)
#expect(!SkillManagementContract.ready(blocked))
#expect(SkillManagementContract.needsSetup(blocked))
}
@Test func `platform incompatible skills need setup instead of reporting ready`() {
let blocked = Self.skill(clawhub: nil, platformIncompatible: true)
#expect(!SkillManagementContract.ready(blocked))
#expect(SkillManagementContract.needsSetup(blocked))
}
private static func skill(
clawhub: ClawHubInstalledSkillLink?,
blockedByAgentFilter: Bool? = nil,
platformIncompatible: Bool? = nil) -> SkillStatus
{
SkillStatus(
name: "Weather",
description: "Forecasts",
source: "openclaw-managed",
filePath: "/tmp/weather/SKILL.md",
baseDir: "/tmp/weather",
skillKey: "weather",
primaryEnv: nil,
emoji: "☀️",
homepage: nil,
always: false,
disabled: false,
blockedByAgentFilter: blockedByAgentFilter,
platformIncompatible: platformIncompatible,
eligible: true,
requirements: SkillRequirements(bins: [], env: [], config: []),
missing: SkillMissing(bins: [], env: [], config: []),
configChecks: [],
install: [],
clawhub: clawhub)
}
}