fix(supply-chain): verify CI and production inputs
Pin external actions, images, toolchains, native archives, and tvOS engine artifacts; enforce fail-closed CI checks and keep website privacy disclosures aligned with shipped behavior.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { createServer, type ViteDevServer } from 'vite';
|
||||
|
||||
import { faqs, faqSchemaMainEntity, watchTogetherFaqAnswer } from '../src/lib/content/faqs';
|
||||
import { csr, prerender } from '../src/routes/privacy/+page';
|
||||
|
||||
let vite: ViteDevServer;
|
||||
let renderedPrivacyHead: string;
|
||||
let renderedPrivacyHtml: string;
|
||||
let privacySource: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
vite = await createServer({
|
||||
root: process.cwd(),
|
||||
appType: 'custom',
|
||||
server: { middlewareMode: true },
|
||||
});
|
||||
const [{ default: privacyPage }, { render }] = await Promise.all([
|
||||
vite.ssrLoadModule('/src/routes/privacy/+page.svelte'),
|
||||
vite.ssrLoadModule('svelte/server'),
|
||||
]);
|
||||
const rendered = render(privacyPage);
|
||||
renderedPrivacyHead = rendered.head;
|
||||
renderedPrivacyHtml = rendered.body;
|
||||
privacySource = await Bun.file('src/routes/privacy/+page.svelte').text();
|
||||
}, 90_000);
|
||||
|
||||
afterAll(async () => {
|
||||
await vite?.close();
|
||||
}, 30_000);
|
||||
|
||||
function visibleText(html: string): string {
|
||||
return html
|
||||
.replace(/<[^>]+>/g, ' ')
|
||||
.replaceAll('&', '&')
|
||||
.replaceAll(''', "'")
|
||||
.replace(/\s+/g, ' ')
|
||||
.trim();
|
||||
}
|
||||
|
||||
describe('privacy page', () => {
|
||||
test('remains a prerendered, server-rendered route', () => {
|
||||
expect(prerender).toBe(true);
|
||||
expect(csr).toBe(false);
|
||||
expect(renderedPrivacyHead).toContain('<title>Privacy Policy - Plezy</title>');
|
||||
expect(renderedPrivacyHead).toContain('<link rel="canonical" href="https://plezy.app/privacy"');
|
||||
});
|
||||
|
||||
test('renders a concise semantic policy without inventory cards or grids', () => {
|
||||
for (const heading of [
|
||||
'Overview',
|
||||
'Data on your device',
|
||||
'Connections and third parties',
|
||||
'Optional Plezy services',
|
||||
'Your choices',
|
||||
'Retention and security',
|
||||
'Contact and changes',
|
||||
]) {
|
||||
expect(renderedPrivacyHtml).toContain(`>${heading}</h2>`);
|
||||
}
|
||||
|
||||
expect(renderedPrivacyHtml.match(/<section/g)).toHaveLength(7);
|
||||
expect(renderedPrivacyHtml).not.toContain('flow-card');
|
||||
expect(renderedPrivacyHtml).not.toContain('<dl');
|
||||
expect(renderedPrivacyHtml).not.toContain('<ul');
|
||||
expect(privacySource).not.toContain('display: grid');
|
||||
expect(visibleText(renderedPrivacyHtml).split(' ').length).toBeLessThan(900);
|
||||
});
|
||||
|
||||
test('uses first-party language and preserves the material privacy boundaries', () => {
|
||||
const text = visibleText(renderedPrivacyHtml);
|
||||
|
||||
expect(text).not.toMatch(/\brepositor(?:y|ies)\b/i);
|
||||
expect(text).not.toMatch(/repository-verified/i);
|
||||
expect(text).toContain('We do not sell personal data or use it for advertising');
|
||||
expect(text).toContain('access tokens and other sign-in data');
|
||||
expect(text).toContain('may contain a server access credential');
|
||||
expect(text).toContain('does not carry the media stream or your media-server credentials');
|
||||
expect(text).toContain('Support-log uploads are always explicit');
|
||||
expect(text).toContain('up to 90 days');
|
||||
expect(text).toContain('retrieved for three days');
|
||||
expect(text).toContain('no more than three hours');
|
||||
expect(renderedPrivacyHtml).toContain('rel="noopener noreferrer"');
|
||||
});
|
||||
|
||||
test('keeps the Watch Together disclosure aligned with visible FAQ content', () => {
|
||||
const faqIndex = faqs.findIndex((faq) => faq.id === 'watch-together');
|
||||
expect(faqIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(faqs[faqIndex]!.answer).toBe(watchTogetherFaqAnswer);
|
||||
expect(faqSchemaMainEntity[faqIndex]!.acceptedAnswer.text).toBe(watchTogetherFaqAnswer);
|
||||
expect(watchTogetherFaqAnswer).toContain('server and media identifiers');
|
||||
expect(watchTogetherFaqAnswer).toContain('does not relay the media stream');
|
||||
expect(watchTogetherFaqAnswer).toContain('custom relay');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user