fix(sonar): locale-aware sorts, linear share and redaction patterns, focus only on controls

This commit is contained in:
Maurice
2026-10-22 18:48:51 +02:00
parent e291735c95
commit c8ce36e9af
14 changed files with 290 additions and 94 deletions
@@ -14,7 +14,8 @@ const SECRET_KEYS = new Set(['email', 'confirmation_number', 'confirmation', 'ph
/** Keys that cost a lot and tell the model nothing it plans with. */
const DROPPED_KEY = /^(image_url|cover_image|avatar|avatar_url|metadata|place_image|trip_cover)$|geometry/;
const EMAIL = /[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g;
// The lookbehind keeps a match from starting inside a run of address characters, so a long run without an @ is read once.
const EMAIL = /(?<![\w.+-])[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g;
type Json = unknown;
@@ -61,7 +61,8 @@ function notesOf(notes: CollabNoteCard[], more = 0): CollabNotesCard | null {
/** When a note was last touched, for the tab's order. */
function touchedAt(row: Row): number {
const stamp = typeof row.updated_at === 'string' && row.updated_at ? row.updated_at : row.created_at;
const ms = typeof stamp === 'string' ? Date.parse(stamp.includes('T') ? stamp : `${stamp.replace(' ', 'T')}Z`) : NaN;
const ms =
typeof stamp === 'string' ? Date.parse(stamp.includes('T') ? stamp : `${stamp.replace(' ', 'T')}Z`) : Number.NaN;
return Number.isNaN(ms) ? 0 : ms;
}
@@ -47,7 +47,7 @@ function emptyCard(): VacayCard {
/** The distinct months of `dates` in order, at most `max`. */
function monthsOfDates(dates: readonly string[], max: number): CardMonth[] {
const keys = [...new Set([...dates].sort().map(monthKey))].slice(0, max);
const keys = [...new Set([...dates].sort((a, b) => a.localeCompare(b)).map(monthKey))].slice(0, max);
return keys.map((key) => monthOf(`${key}-01`));
}
@@ -58,7 +58,9 @@ function monthsOfDates(dates: readonly string[], max: number): CardMonth[] {
* from January to December, widened to the months the answer holds days of.
*/
function focusMonths(input: AnswerInput, entries: readonly VacayEntryRow[], year: number): CardMonth[] {
const span = [`${year}-01`, `${year}-12`, ...entries.map((entry) => monthKey(entry.date))].sort();
const span = [`${year}-01`, `${year}-12`, ...entries.map((entry) => monthKey(entry.date))].sort((a, b) =>
a.localeCompare(b),
);
const first = span[0] ?? `${year}-01`;
const last = span[span.length - 1] ?? `${year}-12`;
const inside = (key: string) => key >= first && key <= last;
@@ -36,7 +36,7 @@ export function inMonths(date: string, months: readonly CardMonth[]): boolean {
/** The dated days of the trip, from its days or else its first and last date (a year of them at most). */
function tripDatesOf(card: TripCard): string[] {
const dated = card.days.flatMap((day) => (day.date ? [day.date] : []));
if (dated.length > 0) return [...new Set(dated)].sort();
if (dated.length > 0) return [...new Set(dated)].sort((a, b) => a.localeCompare(b));
if (!card.startDate || !card.endDate || card.startDate > card.endDate) return [];
const dates: string[] = [];
const day = new Date(`${card.startDate}T00:00:00Z`);
@@ -137,7 +137,7 @@ function round(value: number, digits: number): number {
export function bitsToHex(bits: string): string | null {
if (!/^[01]{64}$/.test(bits)) return null;
let out = '';
for (let i = 0; i < 64; i += 4) out += parseInt(bits.slice(i, i + 4), 2).toString(16);
for (let i = 0; i < 64; i += 4) out += Number.parseInt(bits.slice(i, i + 4), 2).toString(16);
return out;
}
@@ -137,8 +137,9 @@ export class HikeRoutingService {
userId: number,
tripId: number,
request: HikePlanRequest,
{ grades: withGrades }: HikePlanOptions = { grades: true },
options?: HikePlanOptions,
): Promise<PlannedHike> {
const withGrades = options?.grades ?? true;
// The tools check their own requests; a change plans through the points a hike has saved, held here to the same.
const checked = hikePlanRequestSchema.safeParse(request);
if (!checked.success) throw badRequest(checked.error.issues.map((issue) => issue.message).join('; '));
@@ -29,6 +29,14 @@ describe('redactForProvider', () => {
});
});
it('ASTRED-003: masks an address inside a long run of address characters, and reads a run without one quickly', () => {
expect(redactForProvider('x'.repeat(50) + '.y@mail.example.test!')).toBe('[hidden]!');
const started = Date.now();
redactForProvider('a'.repeat(100000));
redactForProvider('a@'.repeat(50000));
expect(Date.now() - started).toBeLessThan(2000);
});
it("ASTRED-002: drops a booked place's picture and the trip's cover, wherever they sit", () => {
expect(
redactForProvider({