|
1 | 1 | import { describe, expect, it } from 'vitest' |
2 | 2 | import { selectDshTarget } from './dsh-release-policy.mjs' |
3 | 3 |
|
4 | | -const metadata = (versions, latest) => ({ |
5 | | - versions: Object.fromEntries(versions.map(version => [version, {}])), |
| 4 | +const metadata = (versions, latest, dependencies = {}) => ({ |
| 5 | + versions: Object.fromEntries(versions.map(version => [version, { |
| 6 | + dependencies: dependencies[version] ?? {}, |
| 7 | + }])), |
6 | 8 | 'dist-tags': { latest }, |
7 | 9 | }) |
8 | 10 |
|
| 11 | +const registryOf = (entries) => async (name) => { |
| 12 | + const found = entries[name] |
| 13 | + if (found === undefined) throw new Error(`unexpected registry lookup: ${name}`) |
| 14 | + return found |
| 15 | +} |
| 16 | + |
9 | 17 | describe('selectDshTarget', () => { |
10 | | - it('ignores an unpromoted prerelease from the next release line', () => { |
| 18 | + it('ignores an unpromoted prerelease from the next release line', async () => { |
11 | 19 | const registry = metadata(['0.1.1-rc.2', '0.1.2-alpha.2'], '0.1.1-rc.2') |
12 | 20 |
|
13 | | - expect(selectDshTarget(registry, '^0.1.1-rc.2')).toBe('0.1.1-rc.2') |
| 21 | + await expect(selectDshTarget(registry, '^0.1.1-rc.2')).resolves.toBe('0.1.1-rc.2') |
14 | 22 | }) |
15 | 23 |
|
16 | | - it('keeps tracking an untagged prerelease on the current core version', () => { |
| 24 | + it('keeps tracking an untagged prerelease on the current core version', async () => { |
17 | 25 | const registry = metadata(['0.1.0-rc.7', '0.1.0-rc.8'], '0.1.0-rc.7') |
18 | 26 |
|
19 | | - expect(selectDshTarget(registry, '^0.1.0-rc.7')).toBe('0.1.0-rc.8') |
| 27 | + await expect(selectDshTarget(registry, '^0.1.0-rc.7')).resolves.toBe('0.1.0-rc.8') |
20 | 28 | }) |
21 | 29 |
|
22 | | - it('follows latest when it explicitly promotes a new release line', () => { |
| 30 | + it('follows latest when it explicitly promotes a new release line', async () => { |
23 | 31 | const registry = metadata( |
24 | 32 | ['0.1.1-rc.2', '0.1.2-alpha.2', '0.2.0', '0.3.0-alpha.1'], |
25 | 33 | '0.2.0', |
26 | 34 | ) |
27 | 35 |
|
28 | | - expect(selectDshTarget(registry, '^0.1.1-rc.2')).toBe('0.2.0') |
| 36 | + await expect(selectDshTarget(registry, '^0.1.1-rc.2')).resolves.toBe('0.2.0') |
29 | 37 | }) |
30 | 38 |
|
31 | | - it('orders numeric prerelease identifiers with SemVer rules', () => { |
| 39 | + it('orders numeric prerelease identifiers with SemVer rules', async () => { |
32 | 40 | const registry = metadata(['0.1.1-alpha.2', '0.1.1-alpha.10'], '0.1.1-alpha.2') |
33 | 41 |
|
34 | | - expect(selectDshTarget(registry, '^0.1.1-alpha.2')).toBe('0.1.1-alpha.10') |
| 42 | + await expect(selectDshTarget(registry, '^0.1.1-alpha.2')).resolves.toBe('0.1.1-alpha.10') |
35 | 43 | }) |
36 | 44 |
|
37 | | - it('rejects an invalid current range', () => { |
| 45 | + it('rejects an invalid current range', async () => { |
38 | 46 | const registry = metadata(['0.1.1-rc.2'], '0.1.1-rc.2') |
39 | 47 |
|
40 | | - expect(() => selectDshTarget(registry, 'not-a-range')).toThrow(/range/i) |
| 48 | + await expect(selectDshTarget(registry, 'not-a-range')).rejects.toThrow(/range/i) |
41 | 49 | }) |
42 | 50 |
|
43 | | - it('rejects an invalid latest tag', () => { |
| 51 | + it('rejects an invalid latest tag', async () => { |
44 | 52 | const registry = metadata(['0.1.1-rc.2'], 'not-a-version') |
45 | 53 |
|
46 | | - expect(() => selectDshTarget(registry, '^0.1.1-rc.2')).toThrow(/latest/i) |
| 54 | + await expect(selectDshTarget(registry, '^0.1.1-rc.2')).rejects.toThrow(/latest/i) |
| 55 | + }) |
| 56 | + |
| 57 | + it('keeps the current pin when a newer candidate names an unpublished package', async () => { |
| 58 | + const dsh = metadata( |
| 59 | + ['0.1.5-rc.2', '0.1.5-rc.3'], |
| 60 | + '0.1.5-rc.2', |
| 61 | + { |
| 62 | + '0.1.5-rc.2': { '@deepseek-ai/dsh-web-app': '^0.1.5-rc.2' }, |
| 63 | + '0.1.5-rc.3': { '@deepseek-ai/dsh-web-app': '^0.1.5-rc.3' }, |
| 64 | + }, |
| 65 | + ) |
| 66 | + const webApp = metadata( |
| 67 | + ['0.1.5-rc.2', '0.1.5-rc.3'], |
| 68 | + '0.1.5-rc.2', |
| 69 | + { |
| 70 | + '0.1.5-rc.2': { '@deepseek-ai/dsh-client-ui-sidebar-documentpreview': '^0.1.5-rc.2' }, |
| 71 | + '0.1.5-rc.3': { '@deepseek-ai/dsh-client-ui-sidebar-documentpreview': '^0.1.5-rc.3' }, |
| 72 | + }, |
| 73 | + ) |
| 74 | + const preview = metadata(['0.1.5-rc.2'], '0.1.5-rc.2') |
| 75 | + const loadMetadata = registryOf({ |
| 76 | + '@deepseek-ai/dsh-web-app': webApp, |
| 77 | + '@deepseek-ai/dsh-client-ui-sidebar-documentpreview': preview, |
| 78 | + }) |
| 79 | + |
| 80 | + await expect(selectDshTarget(dsh, '^0.1.5-rc.2', loadMetadata)).resolves.toBe('0.1.5-rc.2') |
| 81 | + }) |
| 82 | + |
| 83 | + it('accepts a candidate once every required dependency version is published', async () => { |
| 84 | + const dsh = metadata( |
| 85 | + ['0.1.5-rc.2', '0.1.5-rc.3'], |
| 86 | + '0.1.5-rc.2', |
| 87 | + { '0.1.5-rc.3': { '@deepseek-ai/dsh-web-app': '^0.1.5-rc.3' } }, |
| 88 | + ) |
| 89 | + const webApp = metadata( |
| 90 | + ['0.1.5-rc.3'], |
| 91 | + '0.1.5-rc.3', |
| 92 | + { '0.1.5-rc.3': { 'js-yaml': '^4.2.0' } }, |
| 93 | + ) |
| 94 | + |
| 95 | + await expect(selectDshTarget(dsh, '^0.1.5-rc.2', registryOf({ |
| 96 | + '@deepseek-ai/dsh-web-app': webApp, |
| 97 | + }))).resolves.toBe('0.1.5-rc.3') |
| 98 | + }) |
| 99 | + |
| 100 | + it('fails when the only published release has an unresolvable dependency closure', async () => { |
| 101 | + const dsh = metadata( |
| 102 | + ['0.1.5-rc.3'], |
| 103 | + '0.1.5-rc.3', |
| 104 | + { '0.1.5-rc.3': { '@deepseek-ai/dsh-web-app': '^0.1.5-rc.3' } }, |
| 105 | + ) |
| 106 | + const webApp = metadata(['0.1.5-rc.2'], '0.1.5-rc.2') |
| 107 | + |
| 108 | + await expect(selectDshTarget(dsh, '^0.1.5-rc.3', registryOf({ |
| 109 | + '@deepseek-ai/dsh-web-app': webApp, |
| 110 | + }))).rejects.toThrow(/no installable/) |
47 | 111 | }) |
48 | 112 | }) |
0 commit comments