Skip to content

Commit 98b4f20

Browse files
committed
#208 fix broken tests and lint from merge
1 parent 063f694 commit 98b4f20

20 files changed

Lines changed: 42 additions & 79 deletions

e2e/storage/file-operations.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ test.describe('Storage S3 — File Operations', () => {
9090

9191
// Navigate into dest folder
9292
await rowByName(page, 'target').dblclick();
93-
await expect(page).toHaveURL(new RegExp(`${encodeURIComponent('target')}/?$`));
93+
await expect(page).toHaveURL(/target\/?(?:\?.*)?$/);
9494

9595
// Paste (use keyboard shortcut — right-click in an empty folder
9696
// lands on the ".." row which has no context menu handler)
@@ -238,13 +238,14 @@ test.describe('Storage S3 — File Operations', () => {
238238
}
239239
});
240240

241-
test('rename conflict shows error inside modal', async ({ page }, testInfo) => {
241+
test('rename conflict creates a uniquely named file', async ({ page }, testInfo) => {
242242
const credentials = requireGarageCredentials();
243243
const client = createS3Client(credentials);
244244
const prefix = uniquePrefix(testInfo, 'rename-conflict');
245245
const fileA = `${prefix}a.txt`;
246246
const fileB = `${prefix}b.txt`;
247-
const cleanupKeys = [fileA, fileB];
247+
const renamedFile = `${prefix}b (1).txt`;
248+
const cleanupKeys = [fileA, fileB, renamedFile];
248249

249250
try {
250251
await putTextObject(client, credentials.bucket, fileA, 'file a');
@@ -256,18 +257,15 @@ test.describe('Storage S3 — File Operations', () => {
256257
await rowByName(page, 'a.txt').click({ button: 'right' });
257258
await page.getByRole('menuitem', { name: 'Rename' }).click();
258259

259-
// Try to rename to b.txt (which exists)
260+
// Rename to b.txt, preserving the existing file by assigning a unique name.
260261
const input = page.locator('.modal-box input');
261262
await input.fill('b.txt');
262263
await page.getByRole('button', { name: 'Rename' }).click();
263264

264-
// Modal should stay open and show conflict error
265-
await expect(page.locator('.modal-box')).toBeVisible();
266-
await expect(page.getByText(/already exists/i)).toBeVisible();
267-
268-
// Cancel the modal
269-
await page.getByRole('button', { name: 'Cancel' }).click();
270265
await expect(page.locator('.modal-box')).not.toBeVisible();
266+
await expect.poll(() => objectExists(client, credentials.bucket, fileA)).toBe(false);
267+
await expect.poll(() => objectExists(client, credentials.bucket, fileB)).toBe(true);
268+
await expect.poll(() => objectExists(client, credentials.bucket, renamedFile)).toBe(true);
271269
} finally {
272270
await deleteKnownKeys(client, credentials.bucket, cleanupKeys);
273271
}

playwright.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { defineConfig } from '@playwright/test';
22
import path from 'path';
33

44
const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:4173';
5+
const appPort = new URL(baseURL).port || '80';
56
const chromiumExecutablePath = process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH;
67

78
export default defineConfig({
@@ -35,6 +36,7 @@ export default defineConfig({
3536
},
3637
{
3738
command: 'node --env-file=.env.test build/index.js',
39+
env: { PORT: appPort },
3840
url: baseURL,
3941
reuseExistingServer: false
4042
}

src/lib/editor/completion/completion-history.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,16 @@ function loadHistory(): HistoryStore {
4646
const parsed = JSON.parse(raw) as Partial<HistoryStore>;
4747
const fresh = emptyStore();
4848
for (const category of Object.keys(fresh) as HistoryCategory[]) {
49-
// eslint-disable-next-line security/detect-object-injection
5049
const list = parsed[category];
5150
if (Array.isArray(list)) {
5251
// Sanitize: strings only, deduped, capped.
5352
const seen = new Set<string>();
5453
for (const name of list) {
5554
if (typeof name !== 'string' || seen.has(name)) continue;
5655
seen.add(name);
57-
// eslint-disable-next-line security/detect-object-injection
56+
5857
fresh[category].push(name);
59-
// eslint-disable-next-line security/detect-object-injection
58+
6059
if (fresh[category].length >= CAPS[category]) break;
6160
}
6261
}
@@ -85,13 +84,13 @@ function scheduleSave(): void {
8584
* category. Moves it to the front of the LRU list and persists. */
8685
export function recordUse(category: HistoryCategory, name: string): void {
8786
const history = loadHistory();
88-
// eslint-disable-next-line security/detect-object-injection
87+
8988
const list = history[category];
9089
const index = list.indexOf(name);
9190
if (index === 0) return; // already at the head, nothing to do
9291
if (index > 0) list.splice(index, 1);
9392
list.unshift(name);
94-
// eslint-disable-next-line security/detect-object-injection
93+
9594
if (list.length > CAPS[category]) list.length = CAPS[category];
9695
scheduleSave();
9796
}
@@ -100,7 +99,7 @@ export function recordUse(category: HistoryCategory, name: string): void {
10099
* present. The caller uses this to bias `sortText`. */
101100
export function rankOf(category: HistoryCategory, name: string): number | null {
102101
const history = loadHistory();
103-
// eslint-disable-next-line security/detect-object-injection
102+
104103
const index = history[category].indexOf(name);
105104
return index < 0 ? null : index;
106105
}

src/lib/editor/completion/cursor-context.ts

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ function partsToAlias(parts: string[]): RelationAlias | null {
3838
/** If `pos` points at an `(`, skip past the matching `)` and return the
3939
* position after it. Otherwise return `pos` unchanged. */
4040
function skipOptionalParenGroup(tokens: Token[], pos: number): number {
41-
// eslint-disable-next-line security/detect-object-injection
4241
if (tokens[pos]?.type !== LPAREN) return pos;
4342
return parenGroupEndPosition(tokens, pos).pos;
4443
}
@@ -52,7 +51,6 @@ function skipOptionalParenGroup(tokens: Token[], pos: number): number {
5251
function cursorScopeTokens(tokens: Token[], cursor: number): Token[] | null {
5352
let pos = 0;
5453
while (pos < tokens.length) {
55-
// eslint-disable-next-line security/detect-object-injection
5654
if (tokens[pos].type !== LPAREN) {
5755
pos++;
5856
continue;
@@ -67,7 +65,7 @@ function cursorScopeTokens(tokens: Token[], cursor: number): Token[] | null {
6765
}
6866

6967
const { pos: end, closed } = parenGroupEndPosition(tokens, pos);
70-
// eslint-disable-next-line security/detect-object-injection
68+
7169
const openChar = tokens[pos].start;
7270

7371
// Cursor is inside this body if it's past the opening `(` and either
@@ -100,15 +98,15 @@ export function extractPrefixAtCursor(
10098
): { prefixParts: string[]; wordAtCursor: string } {
10199
// Step 1: find the last token that starts before the cursor.
102100
let pos = tokens.length - 1;
103-
// eslint-disable-next-line security/detect-object-injection
101+
104102
while (pos >= 0 && tokens[pos].start >= cursorInStatement) pos--;
105103
if (pos < 0) return { prefixParts: [], wordAtCursor: '' };
106104

107105
// Step 2: classify that token relative to the cursor. Three cases:
108106
// A) cursor is inside/at the end of an identifier → that's the partial word
109107
// B) cursor is immediately after a dot → no word yet, prefix continues
110108
// C) cursor is attached to something else (keyword, operator) → no prefix
111-
// eslint-disable-next-line security/detect-object-injection
109+
112110
const last = tokens[pos];
113111
const lastEndExclusive = last.stop + 1;
114112
const cursorTouchesLast = lastEndExclusive >= cursorInStatement;
@@ -129,7 +127,7 @@ export function extractPrefixAtCursor(
129127

130128
// Step 3: walk backwards through (DOT IDENTIFIER)* pairs.
131129
const prefixParts: string[] = [];
132-
// eslint-disable-next-line security/detect-object-injection
130+
133131
while (pos >= 1 && tokens[pos].type === DOT && IDENTIFIER_TOKENS.has(tokens[pos - 1].type)) {
134132
prefixParts.unshift(unquoteIdentifier(tokens[pos - 1].text ?? ''));
135133
pos -= 2;
@@ -173,7 +171,6 @@ export function extractAliasMap(
173171
const aliasMap = new Map<string, RelationAlias>();
174172

175173
for (let pos = 0; pos < tokens.length; pos++) {
176-
// eslint-disable-next-line security/detect-object-injection
177174
const token = tokens[pos];
178175

179176
// FROM / JOIN <qualifiedName> [[AS] alias]
@@ -186,11 +183,10 @@ export function extractAliasMap(
186183
// Lowercase for case-insensitive lookup.
187184
aliasMap.set(alias.table.toLowerCase(), alias);
188185
// Optional [AS] <identifier> follows the name.
189-
// eslint-disable-next-line security/detect-object-injection
186+
190187
if (tokens[next]?.type === SqlBaseLexer.AS) next++;
191-
// eslint-disable-next-line security/detect-object-injection
188+
192189
if (tokens[next] && IDENTIFIER_TOKENS.has(tokens[next].type)) {
193-
// eslint-disable-next-line security/detect-object-injection
194190
const aliasName = unquoteIdentifier(tokens[next].text ?? '');
195191
aliasMap.set(aliasName.toLowerCase(), alias);
196192
next++;
@@ -204,20 +200,19 @@ export function extractAliasMap(
204200
// the JSDoc for why the body is skipped).
205201
if (token.type === SqlBaseLexer.WITH) {
206202
let next = pos + 1;
207-
// eslint-disable-next-line security/detect-object-injection
203+
208204
while (next < tokens.length && IDENTIFIER_TOKENS.has(tokens[next].type)) {
209-
// eslint-disable-next-line security/detect-object-injection
210205
const cteName = unquoteIdentifier(tokens[next].text ?? '');
211206
aliasMap.set(cteName.toLowerCase(), { table: cteName });
212207
next++;
213208

214209
next = skipOptionalParenGroup(tokens, next); // optional column list
215-
// eslint-disable-next-line security/detect-object-injection
210+
216211
if (tokens[next]?.type === SqlBaseLexer.AS) next++;
217212
next = skipOptionalParenGroup(tokens, next); // CTE body
218213

219214
// Comma means another CTE follows; anything else ends the WITH list.
220-
// eslint-disable-next-line security/detect-object-injection
215+
221216
if (tokens[next]?.type !== COMMA) break;
222217
next++;
223218
}

src/lib/editor/completion/grammar-analysis.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ const LITERAL_NAMES = (SqlBaseLexer as unknown as { literalNames: (string | null
6363
/** Map a keyword token type back to its canonical uppercase spelling.
6464
* `literalNames` entries look like `"'SELECT'"` (SQL single-quoted). */
6565
function keywordForToken(tokenType: number): string | null {
66-
// eslint-disable-next-line security/detect-object-injection
6766
const literal = LITERAL_NAMES[tokenType];
6867
if (!literal) return null;
6968
const match = /^'(.+)'$/.exec(literal);
@@ -159,7 +158,6 @@ function parseAndAnalyse(sql: string, extendingPrevious: boolean): GrammarAnalys
159158
function lastDefaultChannelTokenIndex(parser: SqlBaseParser): number {
160159
const tokens = (parser.inputStream as CommonTokenStream).getTokens();
161160
for (let i = tokens.length - 1; i >= 0; i--) {
162-
// eslint-disable-next-line security/detect-object-injection
163161
const token = tokens[i];
164162
if (token.channel === Token.DEFAULT_CHANNEL && token.type !== SqlBaseLexer.EOF) {
165163
return token.tokenIndex;

src/lib/editor/format-json.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export function repairTruncatedJson(text: string): string {
55
const openBrackets: string[] = [];
66

77
for (let i = 0; i < result.length; i++) {
8-
// eslint-disable-next-line security/detect-object-injection
98
const ch = result[i];
109
if (escape) {
1110
escape = false;
@@ -41,7 +40,6 @@ export function repairTruncatedJson(text: string): string {
4140
}
4241

4342
for (let i = openBrackets.length - 1; i >= 0; i--) {
44-
// eslint-disable-next-line security/detect-object-injection
4543
result += openBrackets[i] === '{' ? '}' : ']';
4644
}
4745

@@ -55,7 +53,6 @@ export function stripIncompleteTail(json: string): string {
5553
let lastComma = -1;
5654

5755
for (let i = json.length - 1; i >= 0; i--) {
58-
// eslint-disable-next-line security/detect-object-injection
5956
const ch = json[i];
6057
if (esc) {
6158
esc = false;

src/lib/editor/lexer-utils.test.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,12 @@ import { DOT, LPAREN, RPAREN, COMMA, lexSql, readQualifiedName } from './lexer-u
88
describe('implicit token constants match the generated grammar', () => {
99
const names = SqlBaseLexer.literalNames;
1010

11-
// eslint-disable-next-line security/detect-object-injection
1211
it('DOT is "."', () => expect(names[DOT]).toBe("'.'"));
13-
// eslint-disable-next-line security/detect-object-injection
12+
1413
it('LPAREN is "("', () => expect(names[LPAREN]).toBe("'('"));
15-
// eslint-disable-next-line security/detect-object-injection
14+
1615
it('RPAREN is ")"', () => expect(names[RPAREN]).toBe("')'"));
17-
// eslint-disable-next-line security/detect-object-injection
16+
1817
it('COMMA is ","', () => expect(names[COMMA]).toBe("','"));
1918
});
2019

src/lib/editor/lexer-utils.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ export function parenGroupEndPosition(
4646
let depth = 1;
4747
let pos = start + 1;
4848
while (pos < tokens.length && depth > 0) {
49-
// eslint-disable-next-line security/detect-object-injection
5049
if (tokens[pos].type === LPAREN) depth++;
51-
// eslint-disable-next-line security/detect-object-injection
5250
else if (tokens[pos].type === RPAREN) depth--;
5351
pos++;
5452
}
@@ -76,15 +74,13 @@ export function readQualifiedName(
7674
const parts: string[] = [];
7775
let pos = start;
7876

79-
// eslint-disable-next-line security/detect-object-injection
8077
while (pos < tokens.length && IDENTIFIER_TOKENS.has(tokens[pos].type)) {
81-
// eslint-disable-next-line security/detect-object-injection
8278
parts.push(unquoteIdentifier(tokens[pos].text ?? ''));
8379
pos++;
8480

8581
// Continue only when a `DOT IDENTIFIER` pair follows. Anything else
8682
// (end of input, trailing dot, different token) ends the name.
87-
// eslint-disable-next-line security/detect-object-injection
83+
8884
const dotFollows = tokens[pos]?.type === DOT;
8985
const identAfterDot =
9086
tokens[pos + 1] !== undefined && IDENTIFIER_TOKENS.has(tokens[pos + 1].type);

src/lib/editor/split-statements.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ export function splitStatements(sql: string): SqlStatement[] {
4646
let depth = 0;
4747

4848
for (let i = 0; i < tokens.length; i++) {
49-
// eslint-disable-next-line security/detect-object-injection
5049
const token = tokens[i];
5150
const type = token.type;
5251

@@ -111,9 +110,7 @@ export function getStatementAtOffset(sql: string, offset: number): SqlStatement
111110
// If cursor is between statements (on whitespace/semicolons), return the
112111
// previous statement so the user targets what they just finished typing.
113112
for (let i = statements.length - 1; i >= 0; i--) {
114-
// eslint-disable-next-line security/detect-object-injection
115113
if (statements[i].endOffset <= offset) {
116-
// eslint-disable-next-line security/detect-object-injection
117114
return statements[i];
118115
}
119116
}

src/lib/server/migrations/0000_initial_schema.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,4 @@ ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("
6565
CREATE INDEX "user_id_idx" ON "user_storage_connections" USING btree ("user_id");--> statement-breakpoint
6666
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
6767
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
68-
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");
68+
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");

0 commit comments

Comments
 (0)