Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
234 changes: 234 additions & 0 deletions docs/DUPLICATE-CODE-CLEANUP.md

Large diffs are not rendered by default.

447 changes: 444 additions & 3 deletions docs/PROD-VERIFICATION.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions docs/sonarqube.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ ratchets upward as code is touched.
> than our 60% target. `npm run sonar:gate` creates a separate gate with the
> correct value — do not just assign "Sonar way".

**Overall coverage floor: ≥ 80%.** Added once the Phase 1/2 Jest coverage
campaign pushed the whole-repo figure to 81%. This is the one condition in
the gate that is **not** new-code-scoped — Clean-as-You-Code alone can't
prevent the absolute number from drifting down again (a PR that only
touches already-covered lines could still let the overall percentage slip
if untested code is added elsewhere without being flagged as "new" in the
sense Sonar tracks). `npm run sonar:gate` keeps this condition in sync;
re-run it any time to correct drift.

---

## Known flakiness: rare spurious failure in the full suite
Expand Down
12 changes: 10 additions & 2 deletions scripts/sonar-gate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,23 @@ import { getConfig, sonarRequest } from './sonar-api.mjs'

const GATE_NAME = process.env.SONAR_GATE_NAME || 'Aastrika Way'

// All conditions are on NEW code (Clean as You Code). `op` is the FAILING
// comparison: e.g. new_coverage LT 60 means "fail when coverage is below 60".
// All conditions except the last are on NEW code (Clean as You Code). `op` is
// the FAILING comparison: e.g. new_coverage LT 60 means "fail when coverage
// is below 60".
const CONDITIONS = [
{ metric: 'new_coverage', op: 'LT', error: '60', label: 'Coverage >= 60%' },
{ metric: 'new_duplicated_lines_density', op: 'GT', error: '3', label: 'Duplicated lines <= 3%' },
{ metric: 'new_security_rating', op: 'GT', error: '1', label: 'Security rating = A' },
{ metric: 'new_reliability_rating', op: 'GT', error: '1', label: 'Reliability rating = A' },
{ metric: 'new_maintainability_rating', op: 'GT', error: '1', label: 'Maintainability rating = A' },
{ metric: 'new_security_hotspots_reviewed', op: 'LT', error: '100', label: 'Hotspots 100% reviewed' },

// OVERALL (whole-repo) coverage, not new-code. Added once the Phase 1/2
// Jest coverage campaign pushed the absolute figure to 81% — this condition
// is a floor so that number can't silently regress on a later PR that adds
// untested code elsewhere in the repo (Clean-as-You-Code alone wouldn't
// catch that, since it only judges lines actually touched by a change).
{ metric: 'coverage', op: 'LT', error: '80', label: 'Overall coverage >= 80%' },
]

/**
Expand Down
16 changes: 16 additions & 0 deletions src/authoring/apis/editor/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { mountRouter } from '../../../test-support/mountRouter'
import { editorApi } from './index'

const agent = () => mountRouter(editorApi)

/**
* @description Verifies editorApi's single passthrough middleware calls
* next() and does not itself handle any route, so an unmatched request
* falls through to a 404.
*/
describe('editorApi', () => {
it('should fall through to a 404 for any request, since no route is registered', async () => {
const response = await agent().get('/getCompleteDetails/123')
expect(response.status).toBe(404)
})
})
31 changes: 31 additions & 0 deletions src/authoring/apis/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const mockEditorApi = jest.fn((_req: unknown, res: any) => res.status(200).send({ mounted: 'editorApi' }))
jest.mock('./editor', () => ({
editorApi: (req: unknown, res: unknown) => mockEditorApi(req, res),
}))

import { mountRouter } from '../../test-support/mountRouter'
import { api } from './index'

const agent = () => mountRouter(api)

/**
* @description Verifies api mounts editorApi under /editor, so a request to
* that sub-path is actually dispatched to editorApi rather than falling
* through unmatched.
*/
describe('api', () => {
it('should mount editorApi under /editor', async () => {
const response = await agent().get('/editor/getCompleteDetails/123')

expect(mockEditorApi).toHaveBeenCalledTimes(1)
expect(response.status).toBe(200)
expect(response.body).toEqual({ mounted: 'editorApi' })
})

it('should not dispatch to editorApi for a path outside /editor', async () => {
const response = await agent().get('/somewhere-else')

expect(mockEditorApi).not.toHaveBeenCalled()
expect(response.status).toBe(404)
})
})
93 changes: 93 additions & 0 deletions src/authoring/authContent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,96 @@ describe('documented bug: proxy error/unhandledRejection listeners accumulate pe
expect(mockProxy.on).toHaveBeenCalledTimes(4)
})
})

describe('GET — position === -1 (no http/https prefix at all)', () => {
it('falls back to the private-content-service host for a bare /content-store/ path', async () => {
mockProxy.web.mockImplementation((_req, res) => res.end())
await agent().get('/content-store/abc/def.json')
expect(mockProxy.web).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ target: 'https://content.test' })
)
})
})

describe('GET — malformed single-slash scheme (http:/ instead of http://)', () => {
it('repairs the URL before proxying', async () => {
mockProxy.web.mockImplementation((_req, res) => res.end())
await agent().get('/http:/cdn.test/content-store/abc/def.json')
expect(mockProxy.web).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ target: 'https://content.test' })
)
})
})

describe('GET — URLs already rewritten to the contentv3/download path', () => {
it('proxies to the content API without re-matching /content-store/ or /content/', async () => {
mockProxy.web.mockImplementation((_req, res) => res.end())
await agent().get('/http://cdn.test/contentv3/download/abc/def.json')
expect(mockProxy.web).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.objectContaining({ target: 'https://content.test' })
)
})
})

describe('proxyCreator listener callback bodies (invoked directly, no live HTTP)', () => {
// The 'error'/'unhandledRejection' handlers are only ever *registered* by a
// live request (mockProxy.on is a jest.fn() no-op), so their bodies never
// execute unless we invoke the captured callback ourselves. Calling the
// exported Router directly with a stub req/res (bypassing supertest/real
// HTTP entirely) lets us do that without touching a real, already-completed
// response object — avoiding any double-send on a real socket.
// tslint:disable-next-line: no-any
const fakeRes = () => {
// tslint:disable-next-line: no-any
const res: any = {}
res.set = jest.fn(() => res)
res.status = jest.fn(() => res)
res.send = jest.fn(() => res)
res.writeHead = jest.fn(() => res)
res.end = jest.fn(() => res)
return res
}

it('sends a 500 response when the registered "error" listener fires with a truthy error', () => {
mockProxy.web.mockImplementation(() => undefined)
// tslint:disable-next-line: no-any
const req: any = { method: 'GET', url: '/https://cdn.test/content-store/abc/def.json' }
const res = fakeRes();

// Router instances are callable middleware functions: router(req, res, next).
// tslint:disable-next-line: no-any
(authContent as any)(req, res, jest.fn())

const errorCall = mockProxy.on.mock.calls.find((call) => call[0] === 'error')
expect(errorCall).toBeDefined()
const errorHandler = errorCall![1]
errorHandler(new Error('boom'))

expect(res.writeHead).toHaveBeenCalledWith(500)
expect(res.end).toHaveBeenCalledWith({ error: 'Failed due to unknown reason' })
})

it('sends a 500 response when the registered "unhandledRejection" listener fires', () => {
mockProxy.web.mockImplementation(() => undefined)
// tslint:disable-next-line: no-any
const req: any = { method: 'GET', url: '/https://cdn.test/content-store/abc/def.json' }
const res = fakeRes();

// tslint:disable-next-line: no-any
(authContent as any)(req, res, jest.fn())

const rejectionCall = mockProxy.on.mock.calls.find((call) => call[0] === 'unhandledRejection')
expect(rejectionCall).toBeDefined()
const rejectionHandler = rejectionCall![1]
rejectionHandler()

expect(res.writeHead).toHaveBeenCalledWith(500)
expect(res.end).toHaveBeenCalledWith('Some error occured')
})
})
120 changes: 120 additions & 0 deletions src/authoring/utils/decode.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* decode.ts is NOT an Express-route file — it exports a single plain,
* synchronous, standalone transform function `decoder` (no axios, no
* res.send, no try/catch). So this test file calls the exported function
* directly and asserts on return values / thrown errors, per the "plain
* functions" style used in ./cdn-url-replacer.test.ts.
*
* `decoder` expects `data` to be a base64 string that, once base64-decoded
* to raw bytes and reinterpreted as a UTF-16LE character stream (via a
* `Uint16Array` view over the same underlying buffer), yields a valid JSON
* string. To build fixtures, `toDecoderInput` below performs the inverse of
* that pipeline: it takes a raw string, writes each character as a 2-byte
* UTF-16LE code unit into a Buffer (mirroring how `decoder` reads pairs of
* bytes back out via the Uint16Array view), then base64-encodes that
* buffer. `encodeValue` layers `JSON.stringify` on top so callers can pass
* plain JS values and get back valid `decoder` input.
*
* No hang/crash/security-bypass patterns apply here: there is no Router, no
* res object, and no try/catch to route around — the only branch is
* "JSON.parse succeeds" vs. "JSON.parse throws", and both are safe to
* exercise live.
*/

import { decoder } from './decode'

/** Encodes a raw string as base64-of-UTF16LE-bytes, the inverse of decoder's internal pipeline. */
function toDecoderInput(raw: string): string {
const buf = Buffer.alloc(raw.length * 2)
for (let i = 0; i < raw.length; i += 1) {
buf.writeUInt16LE(raw.charCodeAt(i), i * 2)
}
return buf.toString('base64')
}

/** Encodes an arbitrary JSON-serializable value into valid decoder() input. */
function encodeValue(value: unknown): string {
return toDecoderInput(JSON.stringify(value))
}

/**
* @description Verifies decoder correctly reverses the UTF-16LE/base64
* encoding pipeline for a range of JSON value shapes, and propagates a
* SyntaxError rather than swallowing it when the decoded bytes are not
* valid JSON.
*/
describe('decoder', () => {
/**
* @description Verifies decoder returns the original value for various
* JSON-serializable inputs once round-tripped through the encoding helper.
*/
describe('when given a base64 string that decodes to valid JSON', () => {
it('should decode a simple flat object', () => {
const input = { a: 1, b: 'two' }
expect(decoder(encodeValue(input))).toEqual(input)
})

it('should decode an array', () => {
const input = [1, 2, 3, 'four']
expect(decoder(encodeValue(input))).toEqual(input)
})

it('should decode a JSON string value', () => {
const input = 'hello world'
expect(decoder(encodeValue(input))).toEqual(input)
})

it('should decode a nested object with arrays and objects', () => {
const input = {
meta: { count: 2, tags: ['x', 'y'] },
name: 'thumbnail',
nested: { deeper: { value: true } },
}
expect(decoder(encodeValue(input))).toEqual(input)
})

it('should decode a numeric value', () => {
expect(decoder(encodeValue(42))).toEqual(42)
})

it('should decode a boolean value', () => {
expect(decoder(encodeValue(true))).toEqual(true)
})

it('should decode a null value', () => {
expect(decoder(encodeValue(null))).toBeNull()
})

it('should decode an empty object', () => {
expect(decoder(encodeValue({}))).toEqual({})
})

it('should decode a string containing unicode characters', () => {
const input = { greeting: 'héllo wörld 😀' }
expect(decoder(encodeValue(input))).toEqual(input)
})
})

/**
* @description Verifies decoder throws (rather than swallowing) a
* SyntaxError when the base64-decoded, UTF-16-reinterpreted bytes do not
* form valid JSON — the source has no try/catch, so the error must
* propagate synchronously to the caller.
*/
describe('when the decoded bytes are not valid JSON', () => {
it('should throw a SyntaxError for a non-JSON plain string', () => {
const invalidInput = toDecoderInput('not valid json')
expect(() => decoder(invalidInput)).toThrow(SyntaxError)
})

it('should throw for an empty raw string', () => {
const invalidInput = toDecoderInput('')
expect(() => decoder(invalidInput)).toThrow()
})

it('should throw for a malformed/truncated JSON object', () => {
const invalidInput = toDecoderInput('{"a":1,')
expect(() => decoder(invalidInput)).toThrow(SyntaxError)
})
})
})
35 changes: 35 additions & 0 deletions src/authoring/utils/read-meta-and-json/channel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
jest.mock('../S3/read', () => ({
readFromS3: jest.fn(),
}))

import { readFromS3 } from '../S3/read'
import { extractChannelData } from './channel'

const mockReadFromS3 = readFromS3 as jest.Mock

beforeEach(() => {
mockReadFromS3.mockReset()
})

/**
* @description Verifies extractChannelData delegates to readFromS3 with the
* given URL and returns/propagates its result.
*/
describe('extractChannelData', () => {
it('should resolve with the data readFromS3 resolves with', async () => {
mockReadFromS3.mockResolvedValue({ channel: 'c1' })

const result = await extractChannelData('https://s3.test/channel.json')

expect(mockReadFromS3).toHaveBeenCalledWith('https://s3.test/channel.json')
expect(result).toEqual({ channel: 'c1' })
})

it('should propagate a rejection from readFromS3', async () => {
mockReadFromS3.mockRejectedValue(new Error('s3 unavailable'))

await expect(extractChannelData('https://s3.test/channel.json')).rejects.toThrow(
's3 unavailable'
)
})
})
30 changes: 30 additions & 0 deletions src/authoring/utils/upload-meta-and-json/channel.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
jest.mock('../S3/upload', () => ({
uploadToS3: jest.fn(),
}))

import { uploadToS3 } from '../S3/upload'
import { uploadChannelData } from './channel'

const mockUploadToS3 = uploadToS3 as jest.Mock

beforeEach(() => {
mockUploadToS3.mockReset()
})

/**
* @description Verifies uploadChannelData delegates to uploadToS3 with the
* request's data/path and the fixed 'channel.json' filename.
*/
describe('uploadChannelData', () => {
it('should upload the given data/path with the channel.json filename', async () => {
mockUploadToS3.mockResolvedValue({ artifactUrl: 'a', downloadUrl: 'd', error: null })

const result = await uploadChannelData({
data: { name: 'Channel One' },
path: 'content/type/id',
} as any)

expect(mockUploadToS3).toHaveBeenCalledWith({ name: 'Channel One' }, 'content/type/id', 'channel.json')
expect(result).toEqual({ artifactUrl: 'a', downloadUrl: 'd', error: null })
})
})
Loading
Loading