fix(seatable): accept full 2xx status range in client

- getBaseToken / fetchNotes / fetchRecord / batchUpdate의 HTTP 성공 판정을
  `!== 200`에서 `< 200 || >= 300`으로 확장
- SeaTableMetadataCache와 동일한 2xx 처리로 일치 — 일부 기업 프록시가
  200 응답을 201/202로 재작성하는 환경에서 동기화 실패하던 문제 대응
- fetchNotes(토큰+rows 2xx)·batchUpdate(2xx) 회귀 테스트 2건 추가
This commit is contained in:
uppinote 2026-05-29 19:22:40 +09:00
parent bba5e1d55b
commit 2a073b0936
2 changed files with 30 additions and 4 deletions

View file

@ -150,7 +150,7 @@ export class SeaTableClient implements DatabaseProvider {
}),
);
if (response.status !== 200) {
if (response.status < 200 || response.status >= 300) {
throw new Error(
`Failed to obtain SeaTable Base-Token: ${extractApiErrorDetails(response)}`,
);
@ -220,7 +220,7 @@ export class SeaTableClient implements DatabaseProvider {
requestUrl({ url, method: 'GET', headers }),
);
if (response.status !== 200) {
if (response.status < 200 || response.status >= 300) {
throw new Error(`Failed to fetch SeaTable rows: ${extractApiErrorDetails(response)}`);
}
@ -256,7 +256,7 @@ export class SeaTableClient implements DatabaseProvider {
if (response.status === 404) return null;
if (response.status !== 200) {
if (response.status < 200 || response.status >= 300) {
throw new Error(`Failed to fetch SeaTable row ${recordId}: ${extractApiErrorDetails(response)}`);
}
@ -301,7 +301,7 @@ export class SeaTableClient implements DatabaseProvider {
requestUrl({ url, method: 'PUT', headers, body }),
);
if (response.status !== 200) {
if (response.status < 200 || response.status >= 300) {
const errorDetails = extractApiErrorDetails(response);
return buildBatchFailures(updates, `Failed to batch update SeaTable rows: ${errorDetails}`);
}

View file

@ -265,6 +265,20 @@ describe('SeaTableClient', () => {
await expect(client.fetchNotes()).rejects.toThrow(/Failed to fetch SeaTable rows/);
});
it('accepts 2xx statuses other than 200 for token and rows (proxy may rewrite 200→201)', async () => {
// Corporate proxies in front of SeaTable sometimes rewrite 200→201/202.
// Both the Base-Token exchange and the rows fetch must treat the full
// 2xx range as success — mirrors SeaTableMetadataCache.
mockRequestUrl
.mockResolvedValueOnce(mockResponse(BASE_TOKEN_RESPONSE, 201))
.mockResolvedValueOnce(mockResponse({ rows: [{ _id: 'r1', Name: 'Note 1' }] }, 202));
const notes = await client.fetchNotes();
expect(notes).toHaveLength(1);
expect(notes[0].id).toBe('r1');
});
});
describe('fetchRecord', () => {
@ -439,6 +453,18 @@ describe('SeaTableClient', () => {
expect(results[0].error).toBe('Connection lost');
}
});
it('accepts 2xx statuses other than 200 as success (e.g. 201 from a proxy)', async () => {
mockRequestUrl
.mockResolvedValueOnce(mockResponse(BASE_TOKEN_RESPONSE))
.mockResolvedValueOnce(mockResponse({ success: true }, 201));
const results = await client.batchUpdate([{ recordId: 'r1', fields: { Name: 'A' } }]);
expect(results).toEqual([
{ success: true, recordId: 'r1', updatedFields: { Name: 'A' } },
]);
});
});
describe('reconfigure', () => {