import { jest } from '@jest/globals' import type { Descriptor } from '@sigstore/oci' // Mock functions const mockGetOctokit = jest.fn() // eslint-disable-next-line @typescript-eslint/no-explicit-any const mockAttest = jest.fn<() => Promise>() const mockCreateStorageRecord = jest.fn<() => Promise>() const mockGetRegistryCredentials = jest.fn() const mockAttachArtifactToImage = jest.fn<() => Promise>() // Mock @actions/github jest.unstable_mockModule('@actions/github', () => ({ getOctokit: mockGetOctokit, context: { repo: { owner: 'foo', repo: 'bar' }, payload: { repository: { visibility: 'private' } } } })) // Mock @actions/attest jest.unstable_mockModule('@actions/attest', () => ({ attest: mockAttest, createStorageRecord: mockCreateStorageRecord })) // Mock @sigstore/oci jest.unstable_mockModule('@sigstore/oci', () => ({ getRegistryCredentials: mockGetRegistryCredentials, attachArtifactToImage: mockAttachArtifactToImage })) // Dynamic imports after mocking const { createAttestation, repoOwnerIsOrg } = await import('../src/attest') const subjectName = 'ghcr.io/foo/bar' const subjectDigest = 'sha256:7d070f6b64d9bcc530fe99cc21eaaa4b3c364e0b2d367d7735671fa202a03b32' const predicate = { type: 'https://in-toto.io/attestation/release/v0.1', params: {} } describe('repoOwnerIsOrg', () => { beforeEach(() => { jest.clearAllMocks() }) it('returns true when repo owner is an organization', async () => { mockGetOctokit.mockReturnValue({ rest: { repos: { get: jest .fn<() => Promise<{ data: { owner: { type: string } } }>>() .mockResolvedValue({ data: { owner: { type: 'Organization' } } }) } } }) const result = await repoOwnerIsOrg('gh-token') expect(result).toBe(true) }) it('returns false when repo owner is a user', async () => { mockGetOctokit.mockReturnValue({ rest: { repos: { // eslint-disable-next-line @typescript-eslint/no-explicit-any get: jest.fn<() => Promise>().mockResolvedValue({ data: { owner: { type: 'User' } } }) } } }) const result = await repoOwnerIsOrg('gh-token') expect(result).toBe(false) }) }) describe('createAttestation', () => { beforeEach(() => { jest.clearAllMocks() // Default mock implementations mockAttest.mockResolvedValue({ bundle: { mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json' }, certificate: 'cert', tlogID: 'tlog-123', attestationID: 'att-123' }) mockGetRegistryCredentials.mockReturnValue({ username: 'user', password: 'pass' }) mockAttachArtifactToImage.mockResolvedValue({ digest: 'sha256:abc123', mediaType: 'application/vnd.dev.sigstore.bundle.v0.3+json', size: 100 }) }) describe('when createStorageRecord is false', () => { it('skips storage record creation', async () => { const subjects = [ { name: subjectName, digest: { sha256: subjectDigest.replace('sha256:', '') } } ] const result = await createAttestation(subjects, predicate, { sigstoreInstance: 'github', pushToRegistry: true, createStorageRecord: false, githubToken: 'gh-token' }) expect(result.attestationDigest).toBe('sha256:abc123') expect(mockCreateStorageRecord).not.toHaveBeenCalled() }) }) describe('when storage records are empty', () => { beforeEach(() => { mockGetOctokit.mockReturnValue({ rest: { repos: { // eslint-disable-next-line @typescript-eslint/no-explicit-any get: jest.fn<() => Promise>().mockResolvedValue({ data: { owner: { type: 'Organization' } } }) } } }) mockCreateStorageRecord.mockResolvedValue([]) }) it('handles empty storage records gracefully', async () => { const subjects = [ { name: subjectName, digest: { sha256: subjectDigest.replace('sha256:', '') } } ] const result = await createAttestation(subjects, predicate, { sigstoreInstance: 'github', pushToRegistry: true, createStorageRecord: true, githubToken: 'gh-token' }) expect(result.attestationDigest).toBe('sha256:abc123') }) }) describe('when subject has unsupported protocol', () => { beforeEach(() => { mockGetOctokit.mockReturnValue({ rest: { repos: { // eslint-disable-next-line @typescript-eslint/no-explicit-any get: jest.fn<() => Promise>().mockResolvedValue({ data: { owner: { type: 'Organization' } } }) } } }) mockCreateStorageRecord.mockResolvedValue([123]) }) it('handles unsupported protocol gracefully', async () => { const subjects = [ { name: 'http://registry.example.com/foo/bar', digest: { sha256: subjectDigest.replace('sha256:', '') } } ] const result = await createAttestation(subjects, predicate, { sigstoreInstance: 'github', pushToRegistry: true, createStorageRecord: true, githubToken: 'gh-token' }) expect(result.attestationDigest).toBe('sha256:abc123') }) }) })