Accessing currentUser in the API side
私たちのブログが数百万ドル規模の企業に発展したため、私たちはお金を数えるのに忙しく、実際のブログ記事を書く時間がもはやないことに気づきました!ブログ記事を書いてもらうためにライターを雇うことにしましょう。
複数のユーザが記事を作成できるようにするには、何を変更する必要があるのでしょうか?まず、ブログ記事を、それを書いた著者に関連付け、その人の名前を表示できるようにします。また、ブログ記事を読む画面に著者の名前を表示するようにします。最後に、著者が編集できるブログ記事のリストを自分のものだけに制限したいと思います:アリスはボブの記事に変更を加えることができないようにします。
Associating a Post to a User
ここでは、 Post
と User
の間のリレーションシップ(外部キー)を紹介します。これは一対多のリレーション(一人の User
が多くの Post
を持つ)で、先ほど作成した Post
とそれに関連する Comment
のリレーションと同じようなものと考えてください。新しいスキーマはこのようになります:
┌─────────────────────┐ ┌───────────┐
│ User │ │ Post │
├─────────────────────┤ ├───────────┤
│ id │───┐ │ id │
│ name │ │ │ title │
│ email │ │ │ body │
│ hashedPassword │ └──<│ userId │
│ ... │ │ createdAt │
└─────────────────────┘ └───────────┘
このようなデータの変更は、すぐに自然にできるようになります:
- 新しいリレーションシップを
schema.prisma
ファイルに追加 - データベースをマイグレーション
- SDLとサービスを生成/更新
Add the New Relationship to the Schema
まず、新しいフィールド userId
を Post
に追加し、リレーションを User
に追加します:
model Post {
id Int @id @default(autoincrement())
title String
body String
comments Comment[]
user User @relation(fields: [userId], references: [id])
userId Int
createdAt DateTime @default(now())
}
model User {
id Int @id @default(autoincrement())
name String?
email String @unique
hashedPassword String
salt String
resetToken String?
resetTokenExpiresAt DateTime?
roles String @default("moderator")
posts Post[]
}
第4章でブログの認証を設定したときにUserモデルを作成しました。Redwoodの setup auth dbAuth
コマンドは認証を管理する2つのファイルを生成してくれました: api/src/lib/
の auth
ファイルと、 api/src/functions/
の auth
ファイルです。どちらのファイルも PrismaClient を直接使用して User モデルと連携するため、User モデル用の SDL やサービスをセットアップする必要はありませんでした。
Intermissionでの勧めに従って example リポジトリを使用した場合は User SDL とサービスがすでに追加されています。そうでない場合は自分で追加する必要があります:
yarn rw g sdl User --no-crud
GraphQL の User type の機密情報属性をコメントアウトして、漏洩する可能性をなくしておきます:
type User {
...
# hashedPassword: String!
# salt: String!
# resetToken: String
# resetTokenExpiresAt: DateTime
}
type User {
...
# hashedPassword: String!
# salt: String!
# resetToken: String
# resetTokenExpiresAt: DateTime
}
Migrate the Database
次に、変更を適用するためにデータベースをマイグレーションします(オプションがある場合は、マイグレーションに "add userId to post" のような名前を付けてください):
yarn rw prisma migrate dev
Whoops!
User
に roles
を追加したときと同じように、 userId
を必須フィールドにしましたが、開発用データベースにはすでにいくつかのブログ記事があります。userId
のデフォルト値が定義されていないため、このカラムをデータベースに追加できません。
@default(1)
in the schema?これはこの問題を解決してくれますが、将来的に追跡が困難なバグを引き起こす可能性があります:もし post
を user
に割り当てるのを忘れた場合、失敗するどころか喜んで userId
を 1
に設定してしまうでしょう!このような問題を解決するためには、時間をかけて正しい方法で行い、手っ取り早いハックは避けたほうがよいでしょう。未来のあなたは、今のあなたに感謝することでしょう!
開発中なので、データベースは吹っ飛ばしてやり直しましょう:
yarn rw prisma migrate reset
Redwood Tutorial repo から後半のチュートリアルを開始した場合、データベースをリセットした後にエラーが発生します -- Prismaがデータベースにユーザとブログ記事をシードしようとしましたが、シードのブログ記事には新しい必須フィールドである userId
がありません!
scripts/seed.js
を開き、各ブログ記事をに userId: 1
を追加してください:
{
id: 1,
name: 'John Doe',
title: 'Welcome to the blog!',
body:
"I'm baby single- origin coffee kickstarter lo - fi paleo skateboard.Tumblr hashtag austin whatever DIY plaid knausgaard fanny pack messenger bag blog next level woke.Ethical bitters fixie freegan,helvetica pitchfork 90's tbh chillwave mustache godard subway tile ramps art party. Hammock sustainable twee yr bushwick disrupt unicorn, before they sold out direct trade chicharrones etsy polaroid hoodie. Gentrify offal hoodie fingerstache.",
userId: 1,
},
ここで yarn rw prisma migrate reset
を実行すると...違うエラーが表示されます。でも大丈夫です、続きを読んでください...
データベースの reset
を実行しても、保留中のマイグレーションが適用されないので、ここでエラーが発生しました。
つまり、データベースに存在しない userId
を設定しようとしています(Prisma が生成したクライアントライブラリには存在するので、データベースにまだ存在しなくても、存在 するはず だと考えているのです)。
行き詰まったように感じるかもしれませんが、データベースは正常にリセットされ、シードが失敗しただけであることに注意してください。
そこで、新しい userId
を Post
に追加するためにデータベースにマイグレーションを実行し、データベースにデータを投入するためにシードを再実行し、 "add userId to post" のような名前をつけます:
yarn rw prisma migrate dev
そしてシードを実行します:
yarn rw prisma db seed
Redwood Tutorial repo からコードベースを立ち上げなかった場合、データベースにはユーザもブログ記事も1つもありません。 http://localhost:8910/signup にアクセスしてユーザを作成します。ただし、まだブログ記事は作成しないでください! ユーザのロールを "admin" に変更します。previous page で紹介したコンソールを使用するか、 Prisma Studio を開いて直接データベースで変更することができます。
Add Fields to the SDL and Service
新しいリレーションシップをどこに表示するか考えてみましょう。今のところ、ホームページとブログ記事ページだけでしょう:では、ブログ記事の著者をタイトルの横に表示しましょう。 つまり、次のようなGraphQLクエリで、ブログ記事からユーザにアクセスしたいのです:
post {
id
title
body
createdAt
user {
name
}
}
これを可能にするためには、APIサイドで2つの修正を行う必要があります:
posts
SDL にuser
フィールドを追加posts
サービスのuser
に relation resolver を追加
Add User to Posts SDL
type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
user: User!
}
CreatePostInput
や UpdatePostInput
型に user
や userId
を追加することはして いません 。新しく作成されたブログ記事にユーザを設定したいのですが、GraphQL 呼び出しによって誰でもそれを実行できるようにしたくはないのです!GraphQLペイロードを変更するだけで、簡単にブログ記事を作成または編集して、他の誰かに割り当てることができます。ユーザの割り当てをサービスだけに保存して、外部からは操作できないようにします。
ここでは感嘆符付きの User!
を使っていますが、これはすべての Post
がそれに関連するユーザを持つことが分かっているからです -- このフィールドは決して null
にはなりません。
Add User Relation Resolver
これは少しトリッキーです: posts
サービスに "lookup" を追加して、関連するユーザを取得する方法を知る必要があります。comments
SDL とサービスを生成したときに、この relation resolver が作成されました。Post
サービス用のサービスジェネレータを再実行することもできますが、 このファイルに加えた変更が吹っ飛ぶ可能性があります。唯一の選択肢は、ファイルがすでに存在しているので、 --force
フラグを含めることです。これで全て上書きされます。この場合、手動でリゾルバを追加することになります:
import { db } from 'src/lib/db'
export const posts = () => {
return db.post.findMany()
}
export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
})
}
export const createPost = ({ input }) => {
return db.post.create({
data: input,
})
}
export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}
export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}
export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}
これは直感的でない場合があるので、順を追って説明します。まず、このサービスが対象とするモデルと同じ名前の変数を宣言します: posts
サービスでは Post
です。そうしたら、この変数には、検索対象となるフィールドと同じキーを持つオブジェクトを設定します。この場合は user
です。GraphQLがこの関数を呼び出すとき、いくつかの引数を渡します。そのうちのひとつが root
で、これは最初に解決されたオブジェクト、この場合はGraphQLクエリの post
に渡されます:
post { <- root
id
title
body
createdAt
user {
name
}
}
ブログ記事はすでにデータベースから取得されているので、その id
はわかっています。
root
はそのオブジェクトなので、単純に .id
を呼び出してそのプロパティを取得できます。
これで、Prismaで findFirst()
クエリを作成するために必要なことが全てわかりました。すでに見つけたレコードの id
を指定して、 post
自体ではなく、そのレコードに関連する user
を返します。
リゾルバは次のようにも書くことができます:
export const Post = {
user: (_obj, { root }) =>
db.user.findFirst({ where: { id: root.userId } }),
}
上記のリレーションリゾルバを使いながら posts
と post
から返されるブログ記事に user
プロパティを含める場合、このフィールドリゾルバは依然として呼び出され、返されるものはすでに存在する user
プロパティをオーバーライドすることに注意しましょう。なぜでしょうか?それは、GraphQLがどのように動作するかということです -- リゾルバが存在する場合、たとえ root
にすでにそのデータが含まれていても、常にリゾルバが呼び出され、その戻り値が使用されます。
もしあなたがデータベースの設計や検索の経験があるなら、この方法が理想的な解決策ではないことにお気づきかもしれません:ブログ記事が見つかるたびに、その post
に関連するユーザデータを取得するために 追加 のクエリを実行する必要があり、これは N+1 problem とも呼ばれます。これはGraphQLクエリの性質によるものです:各リゾルバ関数は自分の親オブジェクトについてだけ知っていて、潜在的な子オブジェクトについては何も知らないのです。
この問題を回避するために、いくつかの試みがなされてきました。余分な依存関係がないシンプルなものは、このフィールドリゾルバを削除して、データベースから取得した post
と一緒に user
データを含めるだけです:
export const post = ({ id }) => {
return db.post.findUnique({
where: { id },
include: {
user: true
}
})
}
これはあなたの役に立つかもしれないし、役に立たないかもしれません:GraphQLクエリで要求されていなくても常にユーザデータを返すというオーバーヘッドが発生します。 さらに、これはクエリのネスティングを壊すことになります:このブログ記事のユーザと、そのユーザが作成した他のすべてのブログ記事IDのリストを返したいとしたらどうでしょうか?
post {
id
title
body
createdAt
user {
name
posts {
id
}
}
}
このクエリは失敗します。 post.user
しか利用できず、post.user.posts
は利用できないためです。
Redwoodチームは、N+1問題に対するよりエレガントな解決策を積極的に検討していますので、ご期待ください!
Displaying the Author
著者情報を取得するためには、セルのクエリを更新して、ユーザ名を取得しなければなりません。
ブログ記事を公開する場は2つあります:
- ホームページ
- ブログ記事の個別ページ
それぞれのセルを更新して、ブログ記事を作成したユーザの名前を含めるようにしましょう:
export const QUERY = gql`
query ArticlesQuery {
articles: posts {
id
title
body
createdAt
user {
name
}
}
}
`
export const QUERY = gql`
query ArticleQuery($id: Int!) {
article: post(id: $id) {
id
title
body
createdAt
user {
name
}
}
}
`
そして、Articleを表示する表示コンポーネントを更新します:
import { Link, routes } from '@redwoodjs/router'
const Article = ({ article }) => {
return (
<article>
<header>
<h2 className="text-xl text-blue-700 font-semibold">
<Link to={routes.article({ id: article.id })}>{article.title}</Link>
<span className="ml-2 text-gray-400 font-normal">
by {article.user.name}
</span>
</h2>
</header>
<div className="mt-2 text-gray-900 font-light">{article.body}</div>
</article>
)
}
export default Article
Redwood Tutorialのリポジトリから始めたかどうかによって、実際に表示するブログ記事がない場合があります。では、ブログ記事を追加してみましょう!しかし、ブログ記事管理画面あるいはscaffoldでそれを行う前に、実際にユーザをそのユーザが作成したブログ記事に関連付ける必要があります。GraphQL経由で userId
を設定することはできません。これはscaffoldがレコードを作成/編集するときに使用するものです。しかし大丈夫です。これはいずれにせよ、サービスの中だけで行われるようにしたいのです。
Accessing currentUser
on the API side
サービス関数の中では context
という不思議な変数が使えます。この変数には、サービス関数が呼び出されているときのコンテキストが格納されています。このコンテキストで使用できるプロパティのひとつは、ログインしているユーザです(誰かがログインしている のであれば )。これは、Webサイドで利用可能な currentUser
と同じです:
export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id }
})
}
そのため、このリクエストを行ったユーザにアクセスする必要がある場合は、常に context.currentUser
が存在することになります。このユーザの id
を取得し、新しいブログ記事を作成するときに scaffold フォームから入力される残りのデータを適用します。やってみましょう!
これでブログ記事管理画面からブログ記事を作成できるはずです:
そしてホームページに戻ると、実際にブログ記事とその著者が表示されるようになるはずです!
Only Show a User Their Posts in Admin
今のところ /admin/posts
にアクセスした管理者は、自分のブログ記事だけでなく、すべてのブログ記事を見ることができます。これを変更しましょう。
このように context.currentUser
にアクセスできることがわかったので、これをpostsサービス全体に散りばめ、現在ログインしているユーザが所有しているブログ記事だけに返すものを限定することができます:
import { db } from 'src/lib/db'
export const posts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}
export const post = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}
export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}
export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}
export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}
export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}
findUnique()
vs. findFirst()
ここで findUnique()
から findFirst()
に変更したことに注意してください。Prisma の findUnique()
は、 where
節に含まれるすべての属性がユニークインデックスを持つことが前提で、 id
はこの前提を満たしていますが、 userId
は満たしていません。そのため findFirst()
関数に変更する必要があります。この関数では、 where
に好きなものを入れて、複数のレコードを返すことができますが、Prisma はそのセットの最初のものだけを返します。この場合、 userId
に加えて id
で選択しているので、常に1つしかないことが分かっています。
この変更により、ユーザは自分のブログ記事の一覧、または自分が書いたブログ記事の詳細のみを見ることができるようになります。
updatePost
と deletePost
についてはどうでしょうか?
これらは currentUser
だけには制限されていないので、手動で GraphQL を呼び出せば誰でもブログ記事を更新したり削除したりできることになります!それはまずいですね。それらは 少し後で 対処することにしましょう。
しかし、先程のアップデートで問題が発生しました:ホームページは posts
サービスを使用して、ホームページのすべてのブログ記事を表示するのではないでしょうか?このコードの更新によって、ホームページはログインしているユーザのブログ記事だけを表示するように制限され、他のユーザのブログ記事は表示されなくなります!そして、もしログインしていない人がホームページにアクセスしたらどうなるのでしょうか? ERROR
ブログ記事管理画面ではあるブログ記事のリストを返し、ホームページでは別のブログ記事のリストを返すにはどうしたらよいでしょうか?
An AdminPosts Service
GraphQL クエリに変数を追加して、既存の posts
サービスでチェックし、ホームページとブログ記事管理画面のどちらにいるかで異なるブログ記事のリストを返すようにすることもできます。
しかし、この複雑さは、テストしなければならない多くの観点を追加し、将来誰かがそこに入ってきたときに壊れやすくなります -- 新しい条件を追加したり、既存の条件を無効にしたりして、誤ってブログ記事管理機能を悪用されないようにすごく注意しなければなりません。
もしブログ記事管理画面用に 新しい GraphQL クエリを作成したらどうでしょうか?
これらは @requireAdmin
のおかげで自動的にセキュリティチェックが行われ、カスタムコードは必要ありません。
これらの新しいクエリはブログ記事管理画面で使われ、元々のシンプルな posts
サービスはホームページとブログ記事詳細ページで使用されるでしょう。
いくつかのステップを経て、完成させる必要があります:
- 型を定義した
adminPosts
SDL を新規作成 - 新しい
adminPosts
サービスを作成 - ブログ記事管理画面の GraphQL クエリを更新し、
posts
ではなくadminPosts
から取得
Create the adminPosts
SDL
既存の posts.sdl.js
を残し、それを "public" インターフェースとします。その SDL を複製し、名前を adminPosts.sdl.js
とし、以下のように修正します:
export const schema = gql`
type Query {
adminPosts: [Post!]! @requireAuth(roles: ["admin"])
adminPost(id: Int!): Post @requireAuth(roles: ["admin"])
}
input CreatePostInput {
title: String!
body: String!
}
input UpdatePostInput {
title: String
body: String
}
type Mutation {
createPost(input: CreatePostInput!): Post! @requireAuth(roles: ["admin"])
updatePost(id: Int!, input: UpdatePostInput!): Post! @requireAuth(roles: ["admin"])
deletePost(id: Int!): Post! @requireAuth(roles: ["admin"])
}
`
export const schema = gql`
type Post {
id: Int!
title: String!
body: String!
createdAt: DateTime!
user: User!
}
type Query {
posts: [Post!]! @skipAuth
post(id: Int!): Post @skipAuth
}
`
そのため、Post
という単一の型を維持します。なぜなら、その中に含まれるデータは同じであり、どちらの SDL ファイルもこの同じデータ型を返すからです。
一般ユーザはミューテーションにアクセスする必要がないため、posts
SDL からミューテーションを削除することができます。
ミューテーションの作成、更新、削除を新しい adminPosts
SDL に移動し、2つのクエリの名前を posts
から adminPosts
に、post
から adminPost
に変えました。
ご存知でない方のために説明しますと :すべてのクエリ/ミューテーションは、アプリケーション全体で一意な名前でなければなりません!
adminPosts
では、クエリを更新して @skipAuth
ではなく @requireAuth
を使用するようにしました。これでブログ記事管理画面専用のクエリができたので、認証されたときだけアクセスを許可するように鍵をかけることができます。
Create the adminPosts
Service
次に adminPosts
サービスを作成します。SDL の名前をサービスの名前と一致させる必要があるので、作成/更新/削除のミューテーションをそこに移動させる必要があります:
import { db } from 'src/lib/db'
export const adminPosts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}
export const adminPost = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}
export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}
export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id },
})
}
export const deletePost = ({ id }) => {
return db.post.delete({
where: { id },
})
}
(繰り返しますが、 findUnique()
から findFirst()
への変更も忘れないでください。)そして、 posts
を更新して、現在 adminPosts
にある関数のいくつかを削除します:
import { db } from 'src/lib/db'
export const posts = () => {
return db.post.findMany()
}
export const post = ({ id }) => {
return db.post.findUnique({ where: { id } })
}
export const Post = {
user: (_obj, { root }) =>
db.post.findFirst({ where: { id: root.id } }).user(),
}
posts
サービスの userId
検索を削除したので、すべてのブログ記事( posts
) または単一のブログ記事( post
では誰が所有しているかに関係なく)を返すようになりました。
リレーションリゾルバは Post.user
にあり、 adminPosts
にはないことに注意しましょう:どちらの SDL もクエリとミューテーションは Post
を返すので、リレーションリゾルバは元の SDL と名前が一致するサービスに残しておくことになるでしょう: graphql/posts.sdl.js
=> services/posts/posts.js
Update the GraphQL Queries
最後に、新しい adminPosts
と adminPost
クエリを使用するために、いくつかの scaffold コンポーネントを更新する必要があります(スペースを節約するために、以下のコードスニペットは変更点だけです。このページも十分長くなってきました!)。
export const QUERY = gql`
query FindPostById($id: Int!) {
post: adminPost(id: $id) {
id
title
body
createdAt
}
}
`
export const QUERY = gql`
query FindPostById($id: Int!) {
post: adminPost(id: $id) {
id
title
body
createdAt
}
}
`
export const QUERY = gql`
query POSTS {
posts: adminPosts {
id
title
body
createdAt
}
}
`
もし、 posts: adminPosts
という構文を使わなかった場合は、以下の Success
コンポーネントに来る引数を adminPosts
にリネームする必要があるでしょう。この構文では、クエリの結果を posts
にリネームします!
"public" ビュー( ArticleCell
や ArticlesCell
など)は何も変更しなくてよいです。元々の posts
と post
クエリ、およびそれぞれのリゾルバを引き続き使います。
Update and Delete
よし。 updatePost
と deletePost
をに目を向けましょう。なぜこうすることができなかったのでしょうか?
export const updatePost = ({ id, input }) => {
return db.post.update({
data: input,
where: { id, userId: context.currentUser.id },
})
}
なぜなら、 findUnique()
と同様に、Prisma はユニークインデックスを持つフィールドに基づいてレコードを更新したいのであって、この場合は id
だけです。そのため、ここは id
だけにしておく必要があります。しかし、ユーザが自分が所有するレコードだけを更新/削除していることをどのように確認すればよいでしょうか?
まずレコードを select して、ユーザがそのレコードを所有していることを確認し、それから update()
をすることができます:
import { ForbiddenError } from '@redwoodjs/graphql-server'
export const updatePost = async ({ id, input }) => {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}
return db.post.update({
data: input,
where: { id },
})
}
データベースへの別の呼び出しを行うのではなく、adminPost()
サービス関数を使用しています(続ける前にポストがあることを確認するために async/await しなければならなかったことに注意してください)。このようなサービスの組み合わせは、Redwoodでは奨励するように設計されています:サービスの関数はGraphQLのリゾルバとして機能しますが、単なるJSの関数でもあり、必要な場所で呼び出すことができます。そして、なぜこのようなことをしたいのか、その理由がここで明確に示されています:adminPost()
はすでに、見つかったレコードをログインしているユーザが所有するものだけに制限しているので、そのロジックはすでにここにカプセル化されており、管理者が単一のブログ記事で何かをしたいときはいつでも、このコードを実行し、毎回同じロジックを使用することを確認することができます。
これは動作しますが、同じことを deletePost
で行う必要があります。ブログ記事が存在するかどうかのチェックを関数に抜き出してみましょう:
const verifyOwnership = async (id) {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}
}
export const updatePost = async ({ id, input }) => {
await verifyOwnership(id)
return db.post.update({
data: input,
where: { id },
})
}
簡単ですね!最終的な adminPosts
サービスは次のようになります:
import { ForbiddenError } from '@redwoodjs/graphql-server'
import { db } from 'src/lib/db'
const validateOwnership = async ({ id }) => {
if (await adminPost({ id })) {
return true
} else {
throw new ForbiddenError("You don't have access to this post")
}
}
export const adminPosts = () => {
return db.post.findMany({ where: { userId: context.currentUser.id } })
}
export const adminPost = ({ id }) => {
return db.post.findFirst({
where: { id, userId: context.currentUser.id },
})
}
export const createPost = ({ input }) => {
return db.post.create({
data: { ...input, userId: context.currentUser.id },
})
}
export const updatePost = async ({ id, input }) => {
await validateOwnership({ id })
return db.post.update({
data: input,
where: { id },
})
}
export const deletePost = async ({ id }) => {
await validateOwnership({ id })
return db.post.delete({
where: { id },
})
}
Wrapping Up
ふぅ〜!いくつかの異なるシナリオを試し、すべてが期待通りに動作していることを確認しましょう(このようなことのためにQAチームは生きているのです):
- ログアウトしたユーザは、ホームページのすべてのブログ記事を見ることができるはず
- ログアウトしたユーザは、1つのブログ記事の詳細を見ることができるはず
- ログアウトしたユーザは、/admin/postsに行くことができ ない はず
- ログアウトしたユーザは、コメントの横にあるモデレーションコントロールを見ることができ ない はず
- ログインした管理者ユーザは、ホームページのすべての記事を見ることができるはず(自分の記事だけではなく)
- ログインした管理者ユーザは、/admin/postsに行くことができるはず
- ログインした管理者ユーザは、新しいブログ記事を作成することができるはず
- ログインした管理者ユーザは、/admin/posts にある他の人のブログ記事を見ることができ ない はず
- ログインした管理者ユーザは、コメントの横にあるモデレーションコントロールを見ることができ ない はず(最後のページでその動作を変更した場合を除く)
- ログインしたモデレータユーザは、コメントの横にモデレーションコントロールが表示されるはず
- ログインしたモデレータユーザは /admin/posts にアクセスでき ない べき
実際のところ、この機能が将来誤って変更されないようにするために、新しいテストを書くことができます。一番手っ取り早いのは、新しいサービスに対応するために adminPosts.scenarios.js
と adminPosts.test.js
を作成し、指定したユーザが所有するブログ記事のみを返すことを確認することでしょうか。currentUserのモック を使って、誰かがログインしているかどうか、異なるロールでシミュレートすることができます。上で修正したセルのテストを追加することもできますが、これらのテストが取得するデータはサービスから返されるものに依存しているので、サービス自体をカバーしていれば問題ありません。100%カバレッジの人たちはそうではないと主張するでしょうが、彼らがまだテストを書くのに忙しい間、私たちは新しく立ち上げた(妥当な テストカバレッジの)機能による収益のおかげで、新しいヨットでクルージングに出かけているのです!
うまくいきましたか?素晴らしい!何か問題があったのでしょうか?誰かが見過ぎたり、見落としたりしませんか?GraphQLクエリがすべて更新され、開いているすべてのファイルに変更が保存されていることを再確認してください。