Open49

TaskShareApp(バックエンド)エラースクラップ集

むらぴょんむらぴょん

userRepo.find が undefined だから.mockResolvedValue が無いと怒られる

つまり userRepo が想定通りのモックになってないか、find がモックになってない

Hidden comment
むらぴょんむらぴょん

users[randomNumberMoreUsersLength].id ← users に存在しないインデックスrandomNumberMoreUsersLength を参照してるから、users[...] 自体が undefined になって、結果 .id を読もうとして undefined に .id は無い!

Hidden comment
むらぴょんむらぴょん

実際にexpect()に渡された引数(authService.getMe)に対して、toHaveBeenCalledWith()に渡された引数(controller.getMe())が期待する値ではない。

(期待値とズレており、一致していない)

Expected: toHaveBeenCalledWith に渡した値 → req.user → { sub: 1 }
Received: 実際に呼ばれた値 → { user: { sub: 1 } }

Hidden comment
むらぴょんむらぴょん

res.clearCookie(...) が呼ばれてない

このエラーは「res.clearCookie(...) が呼ばれてない」という内容。でもテストの中では clearCookie を呼んでいることを期待している。
Number of calls: 0 → 呼ばれたことが一度もない

Hidden comment
むらぴょんむらぴょん

依存性注入を行うDIコンテナがUserRepositoryを見つけられない

  • @InjectRepository(User) 使ってるなら TypeOrmModule.forFeature([User]) を書いたか?

  • UsersModule の imports にそれが含まれてるか?

  • UserRepository を自作の @Injectable() クラスとしてDIしたいなら、providers に登録したか?

を再度チェックしてみる。

Hidden comment
むらぴょんむらぴょん

「Nest can't resolve dependencies of the UserRepository (?). Please make sure that the argument DataSource at index [0] is available in the TypeOrmModule context.」

UserRepository クラスが DataSource をコンストラクタで受け取ろうとしてるが、 DataSource を Nest がDIできてない。

Hidden comment
むらぴょんむらぴょん

migrationファイルでuserテーブルを作成しようとしてもDBに作成されない。(No migrations are pending)


🔍 ヒント:考えるべきポイント

  • その CreateUsers... マイグレーション、ほんとにまだ未実行?(migrations テーブルに既に記録されとらん?)
     ⇒実行していたため、確認したらpostgresには全然違うテーブルができていた。
  • ファイルの中身を編集したあとに migration:run してない?(既に適用済みのマイグレーションファイルは、再実行されないよ)
     ⇒元々users以外にもテーブル作成のコード(CREATE TABLE…)がファイルに記載されていたため、それらを削除した後にmigration:runを実行していた。
  • マイグレーションファイル、どこの dist/ に出力されとる?(typeorm.config.ts で指定した migrations のパスと一致してる?)migrations: ['dist//migrations//*.js']
     ⇒migrationファイル作成時はdistフォルダ内に出力しておらず、src/migration内に出力してることを確認。
Hidden comment
むらぴょんむらぴょん

No changes in database schema were found

このメッセージが出るときって、TypeORMが「エンティティとDBスキーマが一致してる」って判断しとる状態。

🔍 ヒント:チェックポイント

  1. 最近エンティティの変更、ほんまにあった?
    → 何か @Column を追加・変更・削除した?
  2. その変更、ちゃんと .ts → .js にビルドされとる?
    → npm run build 忘れとらん?
  3. typeorm.config.ts の entities パス、dist/ 指してる?
    → src/ 指してたら反映されんで?
  4. 一部のカラム、 @Column({ name: 'xxx' }) で明示してて差分が検出されとらん可能性もある?
    DB側に typeORM_metadata テーブルはある?そこが前の状態のままやと差分判断ミスるで
Hidden comment
むらぴょんむらぴょん

Postmanのリクエストはパッと見正しく見えるのに、PostgreSQL側では「image_id に null が入ってるぞ💢」って怒られる
QueryFailedError: null value in column "image_id" of relation "users" violates not-null constraint


👣 自分で確認すべき観点

  • Signup時のDTOに image_id の定義はある?
    → DTOが class-validator 付きで定義されてるなら、抜けてる項目は undefined 扱いになる可能性ある。
  • AuthServiceの .create() や .save() に渡すデータオブジェクトに image_id を含めとる?
  • エンティティで image_id に @Column({ nullable: false }) が付いとる?
    → でも、nullが入ってるってことは「バリデーションは通ったけど、実際に値が入ってない」ってこと。
Hidden comment
むらぴょんむらぴょん

TypeError: Converting circular structure to JSONとは??

💥 エラーの本質

このエラーは JSON.stringify() しようとした対象に 循環参照がある ときに出る。
キーワードの circular structure to JSON はまさに「循環参照しとるオブジェクトを JSON.stringify()しようとして爆発」している

🔍 原因の候補

AuthController の該当コード:

auth.controller.ts
return res.cookie('access_token', jwt.accessToken, {
  httpOnly: true,
  secure: true,
  sameSite: 'none',
  path: '/',
});

このままだと res.cookie(...) の返り値(つまり res オブジェクトそのもの)を返却している
これは Express の Response オブジェクト で、内部に socket など循環参照構造を持つ。

Hidden comment
むらぴょんむらぴょん

Error: Unknown authentication strategy "jwt"とは?

🔍 ヒント1:Passportが "jwt" 戦略を認識していない

これは「passport.use('jwt', strategy) をどこかで忘れてない?」ってサイン。

🔧 ヒント2:JwtStrategyの登録忘れ or Moduleに含めてない?

  • JwtStrategy クラス自体は作ってあるか?
  • @Injectable() で定義されてる?
  • AuthModule の providers に入ってる?
  • PassportModule と JwtModule は imports にある?
むらぴょんむらぴょん

🔧 ヒント2 確認結果

  • JwtStrategy クラス自体は作ってあるか?

⇒JwtStrategyっていうファイル自体なく、ほかのファイルにも記述はなかった。

  • @Injectable() で定義されてる?

⇒auth.service.tsで以下の記載はあった。

auth.service.ts 
 private readonly jwt: JwtService,
  • AuthModule の providers に入ってる?

⇒auth.module.tsのproviders にはAuthServiceしか入ってない。

  • PassportModule と JwtModule は imports にある?

⇒PassportModuleはないが、JwtModuleはAuthModule の imports にあった。

🔍 ヒント3:"strategy" っていう単語の意味、意識してみて?

Passportが言っとる "Unknown authentication strategy 'jwt'" の “strategy” は、
あるクラスの中で passport.use('jwt', 〇〇) って登録されとるやつのこと。

🛠️ ヒント4:nestjs/passport + passport-jwt セットアップは手作業で要る

今の状況は「JWTで守られたルートにアクセスしようとしたけど、JWTでの認証方法(= strategy)が定義されてない」ってエラー。
つまり…
👉 "JwtStrategy" というファイルそのものが存在しない
👉 passportに「jwt」戦略を登録してない
ってのが本質。

Hidden comment
むらぴょんむらぴょん

プロパティ 'passReqToCallback' は型 '{ clientID: string | undefined; clientSecret: string | undefined; callbackURL: string; scope: string[]; }' にありませんが、型 'StrategyOptionsWithRequest' では必須です。

🧨 原因

TypeScriptの型推論が「StrategyOptionsWithRequest」を想定してるのに、passReqToCallback を定義してない or 不正な構成になっている。

Hidden comment
むらぴょんむらぴょん

Error: Unknown authentication strategy "google"


NestJS + Passport で Google 認証を使うには、strategy の定義が正しくされていないと今回のような Unknown authentication strategy "google" エラーが出る。

🔍 確認リスト(再チェック)

  • passport-google-oauth20npm i 済みか
  • GoogleStrategy を作成し、 'google' という名前で登録しているか
  • AuthModule の providers に追加しているか
  • .envGOOGLE_CLIENT_ID など必要情報が入っているか
  • callbackURL が GCP 側の OAuth 設定と一致しているか
Hidden comment
むらぴょんむらぴょん

QueryFailedError: invalid input syntax for type uuid: ""


imageId: '' は 空文字 "" を UUID 型に渡している から、PostgreSQL 側で怒られた。

Hidden comment
むらぴょんむらぴょん

プロパティ'secretOrKey'の型に互換性がありません。

🕶️ これは JwtStrategy クラスで secretOrKey の値が undefined になってて、型が合わない(互換性がない) って怒られてるエラー

🔥 エラーのポイント

jwt.strategy.ts
secretOrKey: string | undefined

になってるけど、StrategyOptions 型の中では

jwt.strategy.ts
secretOrKey: string | Buffer

絶対に要求される。だから undefined の可能性があると怒られる。

Hidden comment
むらぴょんむらぴょん

型 '"generateJwt"' の引数を型 'keyof AuthService' のパラメーターに割り当てることはできません。

🔒 なぜそのままじゃダメなの?

TypeScriptの型システムは「private に触るなんて許さん😤」って言ってくるけん、

auth.service.spec.ts
jest.spyOn(service, 'generateJwt');

ってやると、「generateJwtAuthService のパブリックメンバじゃないぞ💢」って型エラーになる。

Hidden comment
むらぴょんむらぴょん

変数 'codeService' は割り当てられる前に使用されています。

codeService を beforeEach で作って Nest の DI にも渡してないのが原因

Hidden comment
むらぴょんむらぴょん

型○○の引数を型△△のパラメーターに割り当てることはできません。

型○○には型△△からの次のプロパティがありません: ......

このエラーの本質はコレ👇

{ id: number; email: string } を、引数型 User | Promise<User> に渡してる

つまり「User丸ごとを受け取る関数」に、idemail だけの部分オブジェクトを渡してる。

Hidden comment
むらぴょんむらぴょん

UnknownDependenciesException [Error]: Nest can't resolve dependencies of the OneTimeCodeService (?). Please make sure that the argument "REDIS" at index [0] is available in the AuthModule context.

いまの問題点

AuthControllerOneTimeCodeServiceConfigServiceクラストークンで注入してる。

AuthModule.providersRedis(ioredis のクラス)を そのまま並べている。
これだと Nest は new Redis() で実体化しようとして、接続情報が無いので失敗、さらに OneTimeCodeService@Inject('REDIS')(or 同等のトークン)を要求している場合は トークン不一致UnknownDependencies(REDIS) が出る。

Hidden comment
むらぴょんむらぴょん

Nest can't resolve dependencies of the AuthController (AuthService, ?, ConfigService). Please make sure that the argument OneTimeCodeService at index [1] is available in the RootTestModule context.

Nestの依存解決エラー

auth.controller.ts
constructor(
  private readonly authService: AuthService,
  private readonly codeService: OneTimeCodeService, // ← index[1]
  private readonly config: ConfigService,
) {}

みたいになってて、テスト用モジュールに OneTimeCodeService のプロバイダが無いから「見つからん!」と怒られてる。

Hidden comment
むらぴょんむらぴょん

"id": 2のユーザー情報でログインしてもログイン中のユーザーを確認したら"id": 1の情報が返ってくる

古いJWT(たぶん Google でログインした時の token)が Cookie に残ってて、Guard がそっちを優先して読んでる。

よくある落とし穴

  1. JwtStrategy が Cookie を先に読む
    ExtractJwt.fromExtractors([...]) の順序で Cookie が先だと、ヘッダーに新しい Bearer を付けても 古い Cookie が優先される。

  2. メール/パスワードのログイン時に Cookie を更新してない

  • Googleログインは res.redirect(...?token=...) でフロントが token を保存
  • 通常ログインは レスポンスBodyに token だけ返してCookieは更新してない
    → そのまま getMe を叩くと、サーバ側の Cookie extractor が古い Cookie を拾う
  1. Cookie のドメイン/パス属性がバラバラ
    Googleフローと通常ログインで domain, path, sameSite, secure が違うと別Cookieとして並存→古い方が読まれることがある

  2. テスト/Postman が Cookie を保持

  • Postmanは自動で Cookie を保存する
むらぴょんむらぴょん

対策

  1. 対策(ヘッダー優先にする)
jwt.strategy.ts
jwtFromRequest: ExtractJwt.fromExtractors([
  // 1. Authorization ヘッダー(Bearer)を最優先
  (req: Request) => {
    const h = req.headers['authorization'];
    return (h && h.startsWith('Bearer ')) ? h.slice(7) : null;
  },
  // 2. Cookie(過去の残骸があっても、ヘッダーがあればそっちを使う)
  (req: Request) => req.cookies?.['access_token'] ?? null,
]),
  1. 対策(通常ログインでも Cookie を必ず上書き保存)
auth.controller.ts
// login(email/password)
const { accessToken } = await this.authService.generateJwt(user.id, user.email);
res.cookie('access_token', accessToken, {
  httpOnly: true,
  sameSite: 'lax',  // devなら 'lax'、クロスオリジンなら 'none' + secure:true
  secure: false,    // httpsなら true
  path: '/',
});
return { accessToken }; // 返してもOK
  1. 対策
  • 両方のフローで まったく同じ属性で access_token をセット
  • 迷ったら domain は指定せず、path: '/' に統一
  1. 対策:Postman の Cookies タブで対象ホストの Cookie を削除してから再実行
むらぴょんむらぴょん

Nest can't resolve dependencies of the AuthService (?). Please make sure that the argument "UserRepository" at index [0] is available in the RootTestModule context.


そこは useClass: Repository が原因やね。Repository は生のクラスだから、Nest がインスタンス化する際に DataSource/EntityManager など周辺依存が必要になる。テストの RootTestModule にはそれらが無いので、解決できずに

“UserRepository at index [0] is available … じゃないよ”

となるやつ。

Hidden comment
むらぴょんむらぴょん

型 'User' からの次のプロパティがありません: hasId, save, remove, softRemove、2 など。

問題の原因

auth.service.spec.tsproviders で渡している userRepo はこのように定義している。

auth.service.spec.ts
const userRepo = {
  create: jest.fn(),
  save: jest.fn(),
} as any;

つまり、このテスト内で AuthService が使う this.userRepo.createjest.fn() そのもの
でも、いまやっているのは、

auth.service.spec.ts
const spyCreate = jest.spyOn(userRepo, 'create').mockReturnValue(createdUser);

これは「既に jest.fn() として作ったメソッドをさらに spyOn でラップしている」状態。
その結果、型的には Repository<User>create を想定しているのに、実際には jest.fn() が入っていて TypeScript が怒っている。

Hidden comment
むらぴょんむらぴょん

TypeError: Cannot redefine property: hash at Function.defineProperty (<anonymous>)


「Cannot redefine property」= すでにモック済みの関数をもう一度 spyOn しようとしたときに出る典型的なエラー

なぜ起きるのか?

  • bcrypt.hash は元々「関数」なんだけど、Jest が jest.spyOn(bcrypt, 'hash') を呼んだ時に内部的に Object.defineProperty でラップをかける。
    -ところが 同じ関数に対してもう一度 spyOn すると、すでに property が定義済みなので再定義エラーになる。
  • 例えば:
    • 別のテストケースで既に spyOn した
    • あるいは jest.mock('bcrypt') で最初からモック化している
      → そういう状況で再度 spyOn するとこのエラー。
Hidden comment