test(suite): cover boundary and multi-target rollback

Signed-off-by: XiaoSeS <87064762+XiaoSeS@users.noreply.github.com>
This commit is contained in:
XiaoSeS 2026-09-09 14:37:47 +08:00
parent c33cd75e7a
commit d15b2583bc
3 changed files with 52 additions and 0 deletions

View file

@ -487,6 +487,38 @@ describe('Suite local lifecycle', () => {
expect(await exists(join(home, '.skillhub', 'inventory.json'))).toBe(false)
})
test('rolls back the first Agent target when the second target commit fails', async () => {
const home = await mkdtemp(join(tmpdir(), 'skillhub-suite-home-'))
const codexRoot = await mkdtemp(join(tmpdir(), 'skillhub-suite-codex-'))
const claudeRoot = await mkdtemp(join(tmpdir(), 'skillhub-suite-claude-'))
const { plan, downloads } = makePlan()
const renameOperation: typeof rename = async (source, target): Promise<void> => {
if (String(source).includes('.skillhub-suite-stage-')
&& String(target) === join(claudeRoot, 'alpha')) {
throw new Error('injected second target failure')
}
await rename(source, target)
}
await expect(installSuite({
registry,
namespace: 'global',
slug: 'starter-pack',
targets: [
{ agent: 'codex', rootDir: codexRoot, scope: 'project', source: 'explicit' },
{ agent: 'claude', rootDir: claudeRoot, scope: 'project', source: 'explicit' }
],
force: false,
home,
client: clientFor(plan, downloads),
renameOperation
})).rejects.toThrow('injected second target failure')
expect(await readdir(codexRoot)).toEqual([])
expect(await readdir(claudeRoot)).toEqual([])
expect(await exists(join(home, '.skillhub', 'inventory.json'))).toBe(false)
})
test('reports retained backup paths when rollback cannot restore a replaced member', async () => {
const home = await mkdtemp(join(tmpdir(), 'skillhub-suite-home-'))
const rootDir = await mkdtemp(join(tmpdir(), 'skillhub-suite-root-'))

View file

@ -6,10 +6,29 @@ import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.stream.LongStream;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class SkillSuiteCompositionPolicyTest {
@Test
void acceptsOneMemberWhenThatMemberIsTheEntry() {
List<SkillSuiteMemberSelection> members = List.of(member(10L, 101L, "1.0.0"));
assertThatCode(() -> SkillSuiteCompositionPolicy.validate(members, 101L))
.doesNotThrowAnyException();
}
@Test
void acceptsExactlyOneHundredMembersWithOneEntry() {
List<SkillSuiteMemberSelection> members = LongStream.rangeClosed(1, 100)
.mapToObj(id -> member(id, id + 1000, "1.0.0"))
.toList();
assertThatCode(() -> SkillSuiteCompositionPolicy.validate(members, 1001L))
.doesNotThrowAnyException();
}
@Test
void rejectsSuiteWithoutEntrySkill() {
List<SkillSuiteMemberSelection> members = List.of(member(10L, 101L, "1.0.0"));

View file

@ -73,6 +73,7 @@ class SkillSuiteDraftServiceTest {
assertThat(saved.getSkillVersionId()).isEqualTo(40L);
assertThat(saved.getPosition()).isZero();
assertThat(saved.getFingerprintSnapshot()).isEqualTo("sha256:abc");
assertThat(saved.isEntry()).isTrue();
});
assertThat(result.version().getOverview()).isEqualTo("## Start here");
verify(publicationValidator).validate(result.suite(), result.version());