diff --git a/frontend/blog_portal/cypress/e2e/admin_actions.cy.ts b/frontend/blog_portal/cypress/e2e/admin_actions.cy.ts deleted file mode 100644 index 7412b2e..0000000 --- a/frontend/blog_portal/cypress/e2e/admin_actions.cy.ts +++ /dev/null @@ -1,99 +0,0 @@ -describe("admin actions", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedAdmin({ - email: "admin@example.com", - displayName: "rootadmin", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "bob@example.com", - displayName: "bob", - password: "longenoughpassword", - }); - cy.seedPostAs({ - email: "alice@example.com", - password: "longenoughpassword", - title: "Alice post", - body: "Body.", - }).as("alicePost"); - }); - - it("admin promotes another user via the profile page", function () { - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice"); - cy.contains("button", "Promote to admin").click(); - - cy.logoutViaApi(); - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Feature in slot 1").should("be.visible"); - }); - }); - - it("admin features a post and it shows up on the home page", function () { - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Feature in slot 1").click(); - cy.contains(".badge", "Featured (slot 1)").should("be.visible"); - }); - - cy.logoutViaApi(); - cy.visit("/"); - cy.contains("h2", "Featured").should("be.visible"); - cy.get(".featured .post-card").should("have.length", 1); - cy.contains(".featured .post-card", "Alice post").should("be.visible"); - }); - - it("admin unfeaturing a post removes it from the home page", function () { - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Feature in slot 1").click(); - cy.contains(".badge", "Featured (slot 1)").should("be.visible"); - cy.contains("button", "Unfeature").click(); - cy.contains(".badge", "Featured").should("not.exist"); - }); - - cy.logoutViaApi(); - cy.visit("/"); - cy.contains("h2", "Featured").should("not.exist"); - }); - - it("a newly promoted admin can also feature posts", function () { - cy.promoteAdmin("bob@example.com"); - cy.loginViaApi({ - email: "bob@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Feature in slot 2").click(); - cy.contains(".badge", "Featured (slot 2)").should("be.visible"); - }); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/confirm_email_page.cy.ts b/frontend/blog_portal/cypress/e2e/confirm_email_page.cy.ts deleted file mode 100644 index 09415f5..0000000 --- a/frontend/blog_portal/cypress/e2e/confirm_email_page.cy.ts +++ /dev/null @@ -1,48 +0,0 @@ -describe("confirm email page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - }); - - it("redirects to /login when no token is present", function () { - cy.visit("/confirm-email"); - cy.location("pathname").should("eq", "/login"); - }); - - it("sets the password and redirects to /login on success", function () { - cy.signupViaApi({ - email: "alice@example.com", - displayName: "alice", - }); - cy.fetchLatestConfirmToken("alice@example.com").then(function (token) { - cy.visit(`/confirm-email?token=${token}`); - cy.get('input[type="password"]').type("longenoughpassword"); - cy.contains("button", "Confirm").click(); - - cy.location("pathname").should("eq", "/login"); - - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - }); - }); - - it("shows the backend error when password is too short", function () { - cy.signupViaApi({ - email: "alice@example.com", - displayName: "alice", - }); - cy.fetchLatestConfirmToken("alice@example.com").then(function (token) { - cy.visit(`/confirm-email?token=${token}`); - cy.get('input[type="password"]') - .invoke("removeAttr", "minlength") - .type("short"); - cy.contains("button", "Confirm").click(); - - cy.contains(".error", "password must be at least 8").should( - "be.visible", - ); - }); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/guest_routes.cy.ts b/frontend/blog_portal/cypress/e2e/guest_routes.cy.ts deleted file mode 100644 index 2f06222..0000000 --- a/frontend/blog_portal/cypress/e2e/guest_routes.cy.ts +++ /dev/null @@ -1,41 +0,0 @@ -describe("guest route guards", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - }); - - it("redirects anonymous visitors away from /users/:displayName/posts/new", function () { - cy.visit("/users/alice/posts/new"); - cy.location("pathname").should("eq", "/login"); - }); - - it("redirects authenticated visitors away from /login", function () { - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/login"); - cy.location("pathname").should("eq", "/"); - }); - - it("redirects authenticated visitors away from /signup", function () { - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/signup"); - cy.location("pathname").should("eq", "/"); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/home_page.cy.ts b/frontend/blog_portal/cypress/e2e/home_page.cy.ts deleted file mode 100644 index 17667c9..0000000 --- a/frontend/blog_portal/cypress/e2e/home_page.cy.ts +++ /dev/null @@ -1,101 +0,0 @@ -describe("home page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "bob@example.com", - displayName: "bob", - password: "longenoughpassword", - }); - cy.seedPostAs({ - email: "alice@example.com", - password: "longenoughpassword", - title: "Alice first", - body: "Alice's first post.", - }); - cy.seedPostAs({ - email: "bob@example.com", - password: "longenoughpassword", - title: "Bob says hi", - body: "Bob's only post.", - }); - cy.seedPostAs({ - email: "alice@example.com", - password: "longenoughpassword", - title: "Alice second", - body: "Alice's second post.", - }); - }); - - it("renders recent posts in newest-first order with author links", function () { - cy.visit("/"); - cy.contains("h2", "Recent posts").should("be.visible"); - cy.get(".recent .post-card").should("have.length", 3); - cy.get(".recent .post-card").first().within(function () { - cy.contains("Alice second"); - cy.contains("a", "alice"); - }); - }); - - it("hides the featured section when no posts are featured", function () { - cy.visit("/"); - cy.contains("h2", "Featured").should("not.exist"); - }); - - it("shows featured posts in slot order once an admin features them", function () { - cy.seedAdmin({ - email: "admin@example.com", - displayName: "rootadmin", - password: "longenoughpassword", - }); - cy.request("/api/posts").then(function (response) { - const posts = response.body.posts as Array<{ - id: number; - title: string; - }>; - const aliceSecond = posts.find(function (entry) { - return entry.title === "Alice second"; - })!; - const bobPost = posts.find(function (entry) { - return entry.title === "Bob says hi"; - })!; - cy.seedFeaturedPost({ - adminEmail: "admin@example.com", - adminPassword: "longenoughpassword", - postId: bobPost.id, - slot: 2, - }); - cy.seedFeaturedPost({ - adminEmail: "admin@example.com", - adminPassword: "longenoughpassword", - postId: aliceSecond.id, - slot: 1, - }); - }); - - cy.visit("/"); - cy.contains("h2", "Featured").should("be.visible"); - cy.get(".featured .post-card").should("have.length", 2); - cy.get(".featured .post-card").eq(0).contains("Alice second"); - cy.get(".featured .post-card").eq(1).contains("Bob says hi"); - }); - - it("searches users by display name and email prefix", function () { - cy.visit("/"); - cy.get('input[type="search"]').type("al"); - cy.get(".results li").should("have.length", 1); - cy.get(".results li").contains("alice"); - - cy.get('input[type="search"]').clear().type("b"); - cy.get(".results li").should("have.length", 1); - cy.get(".results li").contains("bob"); - - cy.get('input[type="search"]').clear().type("zzz"); - cy.get(".results li").should("have.length", 0); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/login_page.cy.ts b/frontend/blog_portal/cypress/e2e/login_page.cy.ts deleted file mode 100644 index 56b7370..0000000 --- a/frontend/blog_portal/cypress/e2e/login_page.cy.ts +++ /dev/null @@ -1,41 +0,0 @@ -describe("login page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - }); - - it("logs in with valid credentials and shows the user in the header", function () { - cy.visit("/login"); - cy.get('input[type="email"]').type("alice@example.com"); - cy.get('input[type="password"]').type("longenoughpassword"); - cy.contains("button", "Log in").click(); - - cy.location("pathname").should("eq", "/"); - cy.get(".app-header").contains("alice").should("be.visible"); - }); - - it("shows an error on wrong password", function () { - cy.visit("/login"); - cy.get('input[type="email"]').type("alice@example.com"); - cy.get('input[type="password"]').type("wrongpassword"); - cy.contains("button", "Log in").click(); - - cy.location("pathname").should("eq", "/login"); - cy.contains(".error", "invalid credentials").should("be.visible"); - }); - - it("redirects authenticated users away from /login", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/login"); - cy.location("pathname").should("eq", "/"); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/new_post_page.cy.ts b/frontend/blog_portal/cypress/e2e/new_post_page.cy.ts deleted file mode 100644 index db9673b..0000000 --- a/frontend/blog_portal/cypress/e2e/new_post_page.cy.ts +++ /dev/null @@ -1,63 +0,0 @@ -describe("new post page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "bob@example.com", - displayName: "bob", - password: "longenoughpassword", - }); - }); - - it("redirects anonymous visitors to /login", function () { - cy.visit("/users/alice/posts/new"); - cy.location("pathname").should("eq", "/login"); - }); - - it("redirects users away from another user's new-post page", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/bob/posts/new"); - cy.location("pathname").should("eq", "/"); - }); - - it("publishes a post and redirects to the new post page", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice/posts/new"); - cy.get('input[type="text"]').type("Hello world"); - cy.get("textarea").type("This is the body of my first post."); - cy.contains("button", "Publish").click(); - - cy.location("pathname").should("match", /^\/posts\/\d+$/); - cy.contains("h1", "Hello world").should("be.visible"); - cy.contains(".body", "This is the body of my first post.").should( - "be.visible", - ); - }); - - it("blocks submission when the title is empty (HTML5 validation)", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice/posts/new"); - cy.get("textarea").type("body only"); - cy.contains("button", "Publish").click(); - - cy.location("pathname").should("eq", "/users/alice/posts/new"); - cy.get('input[type="text"]:invalid').should("exist"); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/post_page.cy.ts b/frontend/blog_portal/cypress/e2e/post_page.cy.ts deleted file mode 100644 index 4bf4a8c..0000000 --- a/frontend/blog_portal/cypress/e2e/post_page.cy.ts +++ /dev/null @@ -1,136 +0,0 @@ -describe("post page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "bob@example.com", - displayName: "bob", - password: "longenoughpassword", - }); - cy.seedPostAs({ - email: "alice@example.com", - password: "longenoughpassword", - title: "Alice post", - body: "Body of alice's post.", - }).as("alicePost"); - }); - - it("renders the post for anonymous visitors with a login CTA", function () { - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("h1", "Alice post").should("be.visible"); - cy.contains("a", "alice").should("be.visible"); - cy.contains("Comments (0)").should("be.visible"); - cy.contains("Log in").should("be.visible"); - cy.get(".comment-form").should("not.exist"); - }); - }); - - it("lets a signed-in user add a comment", function () { - cy.loginViaApi({ - email: "bob@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.get(".comment-form textarea").type("nice post"); - cy.contains(".comment-form button", "Comment").click(); - - cy.contains(".comment-list li", "nice post").should("be.visible"); - cy.contains(".comment-list li", "bob").should("be.visible"); - cy.contains("Comments (1)").should("be.visible"); - }); - }); - - it("only shows the Delete control on comments you can delete", function () { - cy.loginViaApi({ - email: "bob@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.request("POST", `/api/posts/${post.id}/comments`, { - body: "bob's comment", - }); - cy.logoutViaApi(); - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - cy.request("POST", `/api/posts/${post.id}/comments`, { - body: "alice's comment", - }); - - cy.visit(`/posts/${post.id}`); - cy.contains(".comment-list li", "alice's comment") - .find("button.delete") - .should("exist"); - cy.contains(".comment-list li", "bob's comment") - .find("button.delete") - .should("not.exist"); - }); - }); - - it("hides the Delete post control for non-author non-admin users", function () { - cy.loginViaApi({ - email: "bob@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Delete post").should("not.exist"); - cy.contains("button", "Feature in slot 1").should("not.exist"); - }); - }); - - it("lets the author delete their post", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Delete post").click(); - cy.location("pathname").should("eq", "/"); - }); - }); - - it("shows admin controls (delete, feature, unfeature) to admins", function () { - cy.seedAdmin({ - email: "admin@example.com", - displayName: "rootadmin", - password: "longenoughpassword", - }); - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.get<{ id: number }>("@alicePost").then(function (post) { - cy.visit(`/posts/${post.id}`); - cy.contains("button", "Delete post").should("be.visible"); - cy.contains("button", "Feature in slot 1").should("be.visible"); - cy.contains("button", "Feature in slot 2").should("be.visible"); - - cy.contains("button", "Feature in slot 1").click(); - cy.contains(".badge", "Featured (slot 1)").should("be.visible"); - cy.contains("button", "Unfeature").should("be.visible"); - - cy.contains("button", "Unfeature").click(); - cy.contains(".badge", "Featured").should("not.exist"); - }); - }); - - it("shows a not-found panel for unknown post ids", function () { - cy.visit("/posts/9999"); - cy.contains("h1", "Post not found").should("be.visible"); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/profile_page.cy.ts b/frontend/blog_portal/cypress/e2e/profile_page.cy.ts deleted file mode 100644 index 375e3c9..0000000 --- a/frontend/blog_portal/cypress/e2e/profile_page.cy.ts +++ /dev/null @@ -1,86 +0,0 @@ -describe("profile page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.seedConfirmedUser({ - email: "bob@example.com", - displayName: "bob", - password: "longenoughpassword", - }); - cy.seedPostAs({ - email: "alice@example.com", - password: "longenoughpassword", - title: "Alice's only post", - body: "Hello.", - }); - }); - - it("shows posts to anonymous visitors without owner controls", function () { - cy.visit("/users/alice"); - cy.contains("h1", "alice").should("be.visible"); - cy.contains(".post-card", "Alice's only post").should("be.visible"); - cy.contains("button", "New post").should("not.exist"); - cy.contains("button", "Promote to admin").should("not.exist"); - }); - - it("shows the New post button when viewing your own profile", function () { - cy.loginViaApi({ - email: "alice@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice"); - cy.contains("button", "New post").should("be.visible"); - cy.contains("button", "Promote to admin").should("not.exist"); - }); - - it("hides the Promote button from non-admin viewers", function () { - cy.loginViaApi({ - email: "bob@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice"); - cy.contains("button", "Promote to admin").should("not.exist"); - }); - - it("shows the Promote button to an admin viewing someone else", function () { - cy.seedAdmin({ - email: "admin@example.com", - displayName: "rootadmin", - password: "longenoughpassword", - }); - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/alice"); - cy.contains("button", "Promote to admin").should("be.visible"); - }); - - it("hides the Promote button when an admin views their own profile", function () { - cy.seedAdmin({ - email: "admin@example.com", - displayName: "rootadmin", - password: "longenoughpassword", - }); - cy.loginViaApi({ - email: "admin@example.com", - password: "longenoughpassword", - }); - - cy.visit("/users/rootadmin"); - cy.contains("button", "Promote to admin").should("not.exist"); - }); - - it("renders a not-found panel for unknown display names", function () { - cy.visit("/users/nobody"); - cy.contains("h1", "User not found").should("be.visible"); - }); -}); diff --git a/frontend/blog_portal/cypress/e2e/signup_page.cy.ts b/frontend/blog_portal/cypress/e2e/signup_page.cy.ts deleted file mode 100644 index 0752e66..0000000 --- a/frontend/blog_portal/cypress/e2e/signup_page.cy.ts +++ /dev/null @@ -1,67 +0,0 @@ -describe("signup page", function () { - beforeEach(function () { - cy.resetDb(); - cy.clearMail(); - }); - - it("creates an unconfirmed user and redirects to check-email", function () { - cy.visit("/signup"); - cy.get('input[type="email"]').type("alice@example.com"); - cy.get('input[autocomplete="username"]').type("alice"); - cy.contains("button", "Continue").click(); - - cy.location("pathname").should("eq", "/check-email"); - cy.contains("h1", "Check your email").should("be.visible"); - - cy.getMail().then(function (inbox) { - expect(inbox.messages).to.have.length(1); - expect(inbox.messages[0].To[0].Address).to.equal( - "alice@example.com", - ); - }); - }); - - it("surfaces a 409 when the email is already registered", function () { - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.clearMail(); - - cy.visit("/signup"); - cy.get('input[type="email"]').type("alice@example.com"); - cy.get('input[autocomplete="username"]').type("alice2"); - cy.contains("button", "Continue").click(); - - cy.location("pathname").should("eq", "/signup"); - cy.contains(".error", "email already registered").should("be.visible"); - }); - - it("surfaces a 409 when the display name is taken", function () { - cy.seedConfirmedUser({ - email: "alice@example.com", - displayName: "alice", - password: "longenoughpassword", - }); - cy.clearMail(); - - cy.visit("/signup"); - cy.get('input[type="email"]').type("other@example.com"); - cy.get('input[autocomplete="username"]').type("alice"); - cy.contains("button", "Continue").click(); - - cy.location("pathname").should("eq", "/signup"); - cy.contains(".error", "displayName already taken").should("be.visible"); - }); - - it("blocks invalid display name characters client-side", function () { - cy.visit("/signup"); - cy.get('input[type="email"]').type("alice@example.com"); - cy.get('input[autocomplete="username"]').type("Has Spaces"); - cy.contains("button", "Continue").click(); - - cy.location("pathname").should("eq", "/signup"); - cy.get('input[autocomplete="username"]:invalid').should("exist"); - }); -});