sotashimozono_obsidian-remo.../plugin/tests/RpcError.test.ts
sotashimozono eb1215f10f
test: 100% coverage for errorMessage, RpcError, and isPreconditionFailed (#264)
- tests/errorMessage.test.ts (new): covers both branches of errorMessage()
  (Error instance → .message, non-Error → String()) and both branches of
  asError() (Error passthrough, non-Error wrapping), including subclasses,
  null, undefined, and numeric inputs.

- tests/RpcError.test.ts (new): covers RpcError constructor (name/code/
  message/data set correctly, optional data omitted), instanceof Error,
  and is() true/false branches with close numeric codes.
  Also bundles isPreconditionFailed() from src/proto/rpcError.ts: all
  branches tested — PreconditionFailed match, wrong code, missing code
  property, null, string/number primitives, and duck-typed plain object.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:07:04 +09:00

72 lines
2.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { RpcError } from '../src/transport/RpcError';
import { isPreconditionFailed } from '../src/proto/rpcError';
import { ErrorCode } from '../src/proto/types';
describe('RpcError', () => {
it('sets name, code, message, and data from constructor arguments', () => {
const e = new RpcError(ErrorCode.FileNotFound, 'not found', { path: 'foo.md' });
expect(e.name).toBe('RpcError');
expect(e.code).toBe(ErrorCode.FileNotFound);
expect(e.message).toBe('not found');
expect(e.data).toEqual({ path: 'foo.md' });
});
it('data is undefined when the third argument is omitted', () => {
const e = new RpcError(ErrorCode.InternalError, 'oops');
expect(e.data).toBeUndefined();
});
it('is an instance of Error so catch blocks that check instanceof Error still catch it', () => {
expect(new RpcError(ErrorCode.InternalError, 'err')).toBeInstanceOf(Error);
});
it('is() returns true when the code matches', () => {
const e = new RpcError(ErrorCode.FileNotFound, 'not found');
expect(e.is(ErrorCode.FileNotFound)).toBe(true);
});
it('is() returns false when the code does not match', () => {
const e = new RpcError(ErrorCode.FileNotFound, 'not found');
expect(e.is(ErrorCode.PermissionDenied)).toBe(false);
});
it('is() distinguishes between close numeric codes (AuthRequired vs AuthInvalid)', () => {
const e = new RpcError(ErrorCode.AuthRequired, 'auth required');
expect(e.is(ErrorCode.AuthRequired)).toBe(true);
expect(e.is(ErrorCode.AuthInvalid)).toBe(false);
});
});
describe('isPreconditionFailed', () => {
it('returns true when the object has code === PreconditionFailed', () => {
expect(isPreconditionFailed({ code: ErrorCode.PreconditionFailed })).toBe(true);
});
it('returns false for a different numeric error code', () => {
expect(isPreconditionFailed({ code: ErrorCode.FileNotFound })).toBe(false);
});
it('returns false when the object has no code property', () => {
expect(isPreconditionFailed({ message: 'oops' })).toBe(false);
});
it('returns false for null', () => {
expect(isPreconditionFailed(null)).toBe(false);
});
it('returns false for a string primitive', () => {
expect(isPreconditionFailed('PreconditionFailed')).toBe(false);
});
it('returns false for a number primitive', () => {
expect(isPreconditionFailed(ErrorCode.PreconditionFailed)).toBe(false);
});
it('works with a plain object that duck-types a transport error (no RpcError import needed)', () => {
// The function is duck-typed on purpose — SFTP errors arrive as
// plain objects with a numeric `code` field, not as RpcError instances.
const sftpStyle = { code: ErrorCode.PreconditionFailed, message: 'mtime changed' };
expect(isPreconditionFailed(sftpStyle)).toBe(true);
});
});