Upload 5.23
This commit is contained in:
@@ -14,7 +14,8 @@ mongoose.set('strictQuery', false)
|
|||||||
|
|
||||||
logger.info('connecting to', config.MONGODB_URI)
|
logger.info('connecting to', config.MONGODB_URI)
|
||||||
|
|
||||||
mongoose.connect(config.MONGODB_URI)
|
mongoose
|
||||||
|
.connect(config.MONGODB_URI)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.info('connected to MongoDB')
|
logger.info('connected to MongoDB')
|
||||||
})
|
})
|
||||||
@@ -33,7 +34,12 @@ app.use('/api/blogs', blogsRouter)
|
|||||||
app.use('/api/users', usersRouter)
|
app.use('/api/users', usersRouter)
|
||||||
app.use('/api/login', loginRouter)
|
app.use('/api/login', loginRouter)
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV === 'test') {
|
||||||
|
const testingRouter = require('./controllers/testing')
|
||||||
|
app.use('/api/testing', testingRouter)
|
||||||
|
}
|
||||||
|
|
||||||
app.use(middleware.unknownEndpoint)
|
app.use(middleware.unknownEndpoint)
|
||||||
app.use(middleware.errorHandler)
|
app.use(middleware.errorHandler)
|
||||||
|
|
||||||
module.exports = app
|
module.exports = app
|
||||||
|
|||||||
12
part4/bloglist/controllers/testing.js
Normal file
12
part4/bloglist/controllers/testing.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const testingRouter = require('express').Router()
|
||||||
|
const Blog = require('../models/blog')
|
||||||
|
const User = require('../models/user')
|
||||||
|
|
||||||
|
testingRouter.post('/reset', async (request, response) => {
|
||||||
|
await Blog.deleteMany({})
|
||||||
|
await User.deleteMany({})
|
||||||
|
|
||||||
|
response.status(204).end()
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = testingRouter
|
||||||
@@ -6,7 +6,9 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "cross-env NODE_ENV=production node index.js",
|
"start": "cross-env NODE_ENV=production node index.js",
|
||||||
"dev": "cross-env NODE_ENV=development nodemon index.js",
|
"dev": "cross-env NODE_ENV=development nodemon index.js",
|
||||||
"test": "cross-env NODE_ENV=test jest --verbose --runInBand"
|
"test": "cross-env NODE_ENV=test jest --verbose --runInBand",
|
||||||
|
"start:test": "cross-env NODE_ENV=test node index.js"
|
||||||
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ module.exports = {
|
|||||||
browser: true,
|
browser: true,
|
||||||
es6: true,
|
es6: true,
|
||||||
'jest/globals': true,
|
'jest/globals': true,
|
||||||
|
'cypress/globals': true
|
||||||
},
|
},
|
||||||
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
extends: ['eslint:recommended', 'plugin:react/recommended'],
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
@@ -13,7 +14,7 @@ module.exports = {
|
|||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018,
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
plugins: ['react', 'jest'],
|
plugins: ['react', 'jest', 'cypress'],
|
||||||
rules: {
|
rules: {
|
||||||
indent: ['error', 2],
|
indent: ['error', 2],
|
||||||
'linebreak-style': ['error', 'unix'],
|
'linebreak-style': ['error', 'unix'],
|
||||||
|
|||||||
0
part5/bloglist-frontend/controllers/testing.js
Normal file
0
part5/bloglist-frontend/controllers/testing.js
Normal file
9
part5/bloglist-frontend/cypress.config.js
Normal file
9
part5/bloglist-frontend/cypress.config.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { defineConfig } from "cypress";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// implement node event listeners here
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
102
part5/bloglist-frontend/cypress/e2e/spec.cy.js
Normal file
102
part5/bloglist-frontend/cypress/e2e/spec.cy.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
describe('blog app', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
cy.request('POST', 'http://localhost:3001/api/testing/reset')
|
||||||
|
cy.request('POST', 'http://localhost:3001/api/users/', {
|
||||||
|
name: 'Andrew',
|
||||||
|
username: 'andyyyyy',
|
||||||
|
password: '123456',
|
||||||
|
})
|
||||||
|
cy.request('POST', 'http://localhost:3001/api/users/', {
|
||||||
|
name: 'Matti Luukkainen',
|
||||||
|
username: 'mluukkai',
|
||||||
|
password: 'salainen',
|
||||||
|
})
|
||||||
|
cy.visit('http://localhost:3000')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('front page can be opened', function () {
|
||||||
|
cy.contains('Bloglist')
|
||||||
|
cy.contains('Please log in')
|
||||||
|
cy.contains('Username')
|
||||||
|
cy.contains('Password')
|
||||||
|
cy.contains('Login')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('when logged in', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
it('login succeeds with correct credentials', function () {
|
||||||
|
cy.get('.username').type('andyyyyy')
|
||||||
|
cy.get('.password').type('123456')
|
||||||
|
cy.get('.loginButton').click()
|
||||||
|
})
|
||||||
|
|
||||||
|
cy.contains('Andrew logged in')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('a new blog can be created', function () {
|
||||||
|
cy.contains('New blog').click()
|
||||||
|
cy.get('.title').type('a blog created')
|
||||||
|
cy.get('.author').type('cypress')
|
||||||
|
cy.get('.urlAddress').type('cypress.com')
|
||||||
|
cy.get('.createButton').click()
|
||||||
|
cy.contains('a blog created by cypress')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('a blog can be liked', function () {
|
||||||
|
cy.contains('New blog').click()
|
||||||
|
cy.get('.title').type('a blog created')
|
||||||
|
cy.get('.author').type('cypress')
|
||||||
|
cy.get('.urlAddress').type('cypress.com')
|
||||||
|
cy.get('.createButton').click()
|
||||||
|
cy.contains('a blog created by cypress')
|
||||||
|
cy.contains('View').click()
|
||||||
|
cy.contains('+').click()
|
||||||
|
cy.contains('1')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('a blog can be deleted', function () {
|
||||||
|
cy.contains('New blog').click()
|
||||||
|
cy.get('.title').type('a blog created')
|
||||||
|
cy.get('.author').type('cypress')
|
||||||
|
cy.get('.urlAddress').type('cypress.com')
|
||||||
|
cy.get('.createButton').click()
|
||||||
|
cy.contains('a blog created by cypress')
|
||||||
|
cy.contains('View').click()
|
||||||
|
cy.contains('Remove').click()
|
||||||
|
cy.get('html').should('not.contain', 'a blog created by cypress')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('only the creator can delete a blog', function () {
|
||||||
|
cy.contains('New blog').click()
|
||||||
|
cy.get('.title').type('a blog created')
|
||||||
|
cy.get('.author').type('cypress')
|
||||||
|
cy.get('.urlAddress').type('cypress.com')
|
||||||
|
cy.get('.createButton').click()
|
||||||
|
cy.contains('a blog created by cypress')
|
||||||
|
cy.get('.logoutButton').click()
|
||||||
|
cy.get('.username').type('mluukkai')
|
||||||
|
cy.get('.password').type('salainen')
|
||||||
|
cy.get('.loginButton').click()
|
||||||
|
cy.contains('View').click()
|
||||||
|
cy.contains('Remove').click()
|
||||||
|
cy.contains('a blog created by cypress')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('blogs are ordered according to likes', function () {
|
||||||
|
cy.contains('New blog').click()
|
||||||
|
cy.get('.title').type('another blog created')
|
||||||
|
cy.get('.author').type('cypress')
|
||||||
|
cy.get('.urlAddress').type('cypress.com')
|
||||||
|
cy.get('.createButton').click()
|
||||||
|
cy.contains('another blog created by cypress')
|
||||||
|
cy.contains('View').click()
|
||||||
|
cy.contains('+').click()
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.contains('+').click()
|
||||||
|
cy.wait(1000)
|
||||||
|
cy.visit('http://localhost:3000')
|
||||||
|
cy.get('.blog').eq(0).should('contain', 'another blog created by cypress')
|
||||||
|
cy.get('.blog').eq(1).should('contain', 'a blog created by cypress')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
5
part5/bloglist-frontend/cypress/fixtures/example.json
Normal file
5
part5/bloglist-frontend/cypress/fixtures/example.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"name": "Using fixtures to represent data",
|
||||||
|
"email": "hello@cypress.io",
|
||||||
|
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||||
|
}
|
||||||
25
part5/bloglist-frontend/cypress/support/commands.js
Normal file
25
part5/bloglist-frontend/cypress/support/commands.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
20
part5/bloglist-frontend/cypress/support/e2e.js
Normal file
20
part5/bloglist-frontend/cypress/support/e2e.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/e2e.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
2336
part5/bloglist-frontend/package-lock.json
generated
2336
part5/bloglist-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,9 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"eslint": "eslint .",
|
"eslint": "eslint .",
|
||||||
"dev": "NODE_ENV=development nodemon index.js"
|
"dev": "NODE_ENV=development nodemon index.js",
|
||||||
|
"cypress:open": "cypress open",
|
||||||
|
"start:test": "NODE_ENV=test node index.js"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
@@ -41,6 +43,8 @@
|
|||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
|
"cypress": "^12.15.0",
|
||||||
|
"eslint-plugin-cypress": "^2.13.3",
|
||||||
"eslint-plugin-jest": "^27.2.2",
|
"eslint-plugin-jest": "^27.2.2",
|
||||||
"nodemon": "^2.0.22"
|
"nodemon": "^2.0.22"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -107,13 +107,13 @@ const App = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loginForm = () => (
|
const loginForm = () => (
|
||||||
<form onSubmit={handleLogin}>
|
<form onSubmit={handleLogin} className="loginForm">
|
||||||
<div>
|
<div>
|
||||||
Username
|
Username
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={username}
|
value={username}
|
||||||
name="username"
|
className="username"
|
||||||
onChange={({ target }) => setUsername(target.value)}
|
onChange={({ target }) => setUsername(target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -122,11 +122,11 @@ const App = () => {
|
|||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={password}
|
value={password}
|
||||||
name="password"
|
className="password"
|
||||||
onChange={({ target }) => setPassword(target.value)}
|
onChange={({ target }) => setPassword(target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Login</button>
|
<button type="submit" className='loginButton'>Login</button>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -134,19 +134,21 @@ const App = () => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<p> {user.name} logged in </p>
|
<p> {user.name} logged in </p>
|
||||||
<button onClick={handleLogout}>Logout</button>
|
<button onClick={handleLogout} className='logoutButton'>Logout</button>
|
||||||
<Togglable buttonLabel="New blog" ref={newBlogRef}>
|
<Togglable buttonLabel="New blog" ref={newBlogRef}>
|
||||||
<BlogForm createBlog={handleNewBlog} />
|
<BlogForm createBlog={handleNewBlog} />
|
||||||
</Togglable>
|
</Togglable>
|
||||||
<h2> All blogs</h2>
|
<h2> All blogs</h2>
|
||||||
{blogs.map((blog) => (
|
{blogs
|
||||||
<Blog
|
.sort((a, b) => b.likes - a.likes)
|
||||||
key={blog.id}
|
.map((blog) => (
|
||||||
blog={blog}
|
<Blog
|
||||||
setNotification={setNotification}
|
key={blog.id}
|
||||||
removeBlog={handleRemoveBlog}
|
blog={blog}
|
||||||
/>
|
setNotification={setNotification}
|
||||||
))}
|
removeBlog={handleRemoveBlog}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ describe('test for blogs', () => {
|
|||||||
|
|
||||||
const user = userEvent.setup()
|
const user = userEvent.setup()
|
||||||
|
|
||||||
const title = component.container.getElementsByName('title')
|
const title = component.container.querySelector('.title')
|
||||||
const author = component.container.getElementsByName('author')
|
const author = component.container.querySelector('.author')
|
||||||
const url = component.container.getElementsByName('urlAddress')
|
const url = component.container.querySelector('.urlAddress')
|
||||||
const form = component.container.getElementsByName('form')
|
const form = component.container.querySelector('.form')
|
||||||
|
|
||||||
await user.type(title, 'How to be a good programmer')
|
await user.type(title, 'How to be a good programmer')
|
||||||
await user.type(author, 'Andrew Trieu')
|
await user.type(author, 'Andrew Trieu')
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ export const BlogForm = ({ createBlog }) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2> Create new blog</h2>
|
<h2> Create new blog</h2>
|
||||||
<form onSubmit={addBlog} name='form'>
|
<form onSubmit={addBlog} className='form'>
|
||||||
<div>
|
<div>
|
||||||
Title:
|
Title:
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={title}
|
value={title}
|
||||||
name="title"
|
className="title"
|
||||||
onChange={(event) => setTitle(event.target.value)}
|
onChange={(event) => setTitle(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,7 +39,7 @@ export const BlogForm = ({ createBlog }) => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={author}
|
value={author}
|
||||||
name="author"
|
className="author"
|
||||||
onChange={(event) => setAuthor(event.target.value)}
|
onChange={(event) => setAuthor(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -48,11 +48,11 @@ export const BlogForm = ({ createBlog }) => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={urlAddress}
|
value={urlAddress}
|
||||||
name="urlAddress"
|
className="urlAddress"
|
||||||
onChange={(event) => setUrlAddress(event.target.value)}
|
onChange={(event) => setUrlAddress(event.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Create</button>
|
<button type="submit" className='createButton'>Create</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user