mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 21:23:45 +01:00
[clsi] address zod violations (#36199)
* [editor-core] gracefully handle no-op TextOperation * [web] avoid sending sync to pdf requests without a ?file * [validation-tools] log deeper nested violations * [editor-core] fix round trip of EditNoOperation * [web] use custom error for sync to pdf w/o file, fix tests * [validation-tools] include request params in log msg for violation * Revert "[editor-core] gracefully handle no-op TextOperation" This reverts commit c1d37261d5db72b37dbf39428c54edcfb5423f41. * [web] gracefully handle rejected sync to pdf from context menu GitOrigin-RevId: 21de7dc96b23575b0d67b6eb0c68ed9c2f86292b
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
resetReqValidationLoggingForTests,
|
||||
} from '../../../parseReq'
|
||||
import { zz } from '../../../zodHelpers'
|
||||
import serializers from '@overleaf/logger/serializers'
|
||||
|
||||
const RAW_BODY = Symbol.for('overleaf.lockdown.rawBody')
|
||||
const RAW_QUERY = Symbol.for('overleaf.lockdown.rawQuery')
|
||||
@@ -56,6 +57,28 @@ function lockedRequest({
|
||||
|
||||
type WarnCall = [Record<string, any>, string]
|
||||
|
||||
// Mirrors what a real bunyan logger does to each field before writing a log
|
||||
// line -- warnMock only records the raw ctx, so assertions on the logged
|
||||
// output must run it through the same serializers to see what would actually
|
||||
// reach the log (e.g. the req serializer drops req.body).
|
||||
function serializedLogOutput(calls: WarnCall[]) {
|
||||
return JSON.stringify(
|
||||
calls.map(([ctx, msg]) => [
|
||||
Object.fromEntries(
|
||||
Object.entries(ctx).map(([key, value]) => [
|
||||
key,
|
||||
key in serializers
|
||||
? (serializers as Record<string, (v: unknown) => unknown>)[key](
|
||||
value
|
||||
)
|
||||
: value,
|
||||
])
|
||||
),
|
||||
msg,
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
describe('parseReq log-only rollout', () => {
|
||||
// This block intentionally runs before anything else in the file calls
|
||||
// setLogger(), so the module-level logger is still unset here.
|
||||
@@ -296,7 +319,7 @@ describe('parseReq log-only rollout', () => {
|
||||
|
||||
parseReq(req, schema, { logOnly: true })
|
||||
|
||||
expect(JSON.stringify(warnMock.mock.calls)).not.toContain(
|
||||
expect(serializedLogOutput(warnMock.mock.calls)).not.toContain(
|
||||
'SECRET_VALUE_123'
|
||||
)
|
||||
})
|
||||
@@ -311,7 +334,7 @@ describe('parseReq log-only rollout', () => {
|
||||
|
||||
parseReq(req, schema, { logOnly: true })
|
||||
|
||||
const logged = JSON.stringify(warnMock.mock.calls)
|
||||
const logged = serializedLogOutput(warnMock.mock.calls)
|
||||
expect(logged).toContain('extraSecretField')
|
||||
expect(logged).not.toContain('TOP_SECRET_VALUE')
|
||||
})
|
||||
@@ -325,7 +348,7 @@ describe('parseReq log-only rollout', () => {
|
||||
|
||||
parseReq(req, schema, { logOnly: true })
|
||||
|
||||
const logged = JSON.stringify(warnMock.mock.calls)
|
||||
const logged = serializedLogOutput(warnMock.mock.calls)
|
||||
expect(logged).toContain(longKey.slice(0, 64))
|
||||
expect(logged).not.toContain(longKey)
|
||||
})
|
||||
@@ -367,10 +390,50 @@ describe('parseReq log-only rollout', () => {
|
||||
expect(Array.isArray(unionIssue.errors)).toBe(true)
|
||||
expect(unionIssue.errors.length).toBeGreaterThan(0)
|
||||
|
||||
expect(JSON.stringify(warnMock.mock.calls)).not.toContain(
|
||||
expect(serializedLogOutput(warnMock.mock.calls)).not.toContain(
|
||||
'UNION_SECRET_VALUE'
|
||||
)
|
||||
})
|
||||
|
||||
it('expands a union nested 3 levels deep inside other unions', () => {
|
||||
// Mirrors overleaf-editor-core's rawOperation -> rawFile ->
|
||||
// rawFileMetadata shape: a union of objects, one of whose fields is
|
||||
// itself a union of objects, one of whose fields is itself a union.
|
||||
const level3 = z.union([
|
||||
z.strictObject({ a: z.literal('a') }),
|
||||
z.strictObject({ b: z.literal('b') }),
|
||||
])
|
||||
const level2 = z.union([
|
||||
z.strictObject({ inner: level3 }),
|
||||
z.strictObject({ other: z.string() }),
|
||||
])
|
||||
const level1 = z.union([
|
||||
z.strictObject({ mid: level2 }),
|
||||
z.strictObject({ different: z.string() }),
|
||||
])
|
||||
const req = {
|
||||
body: { value: { mid: { inner: { c: 'nope' } } } },
|
||||
} as Request
|
||||
const schema = z.object({ body: z.object({ value: level1 }) })
|
||||
|
||||
parseReq(req, schema, { logOnly: true })
|
||||
|
||||
const [ctx] = warnMock.mock.calls[0] as WarnCall
|
||||
// Walk down: top union (value) -> level1 branch (mid) -> level2
|
||||
// union (inner) -> level3 union, whose own branch errors (unknown
|
||||
// keys "a"/"b" not satisfied by "c") must have survived.
|
||||
const top = ctx.issues.find((i: any) => i.code === 'invalid_union')
|
||||
const level1Errors = top.errors[0] // the "mid" branch
|
||||
const level2Issue = level1Errors.find(
|
||||
(i: any) => i.code === 'invalid_union'
|
||||
)
|
||||
const level2Errors = level2Issue.errors[0] // the "inner" branch
|
||||
const level3Issue = level2Errors.find(
|
||||
(i: any) => i.code === 'invalid_union'
|
||||
)
|
||||
expect(level3Issue.errors).toBeDefined()
|
||||
expect(level3Issue.errors.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user