2015년 7월 29일 수요일

Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

발생현상]

Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

node.js에서 Mocha를 이용해서 테스트 실행중 특정 시점이후 발생하는 오류

node.js에서 MySql에서 사용자 정보 조회, 등록을 5번의 테스트 case로 수행함.
이때 DB Connection Pool의 connectionLimit은 "3"으로 설정되어 있음. 

test case : 회원가입
    describe("회원가입 ->", function() {
        it ("회원가입 실패(필수값 누락)", function(done) {
            var data = {
                user_id: user_id,
                password : password,
                user_name : '김치손',
                email : '',
                phone : '',
                cellphone : '',
                sex : '',
                logintype : mem_type
            };
            request(svr)
                .post("/member/register")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    expect('ER_BAD_NULL_ERROR').to.equal(res.body.code);
                    done();
                });
        });


routes.js : 
// 회원 정보를 저장
router.post('/register', function(req, res, next) {
    console.log(req.body);

    var loginType = req.body.logintype;
    var vUserId = req.body.user_id;

    var datas = [
        req.body.user_id,
        req.body.user_name,
        req.body.password,
        req.body.nickname,
        req.body.email,
        req.body.sex,
        req.body.phone,
        req.body.address,
        req.body.logintype
    ];
    // TODO 이메일 중복, 체크 로직 추가

        memberProvider.insertMember(datas, function(err, sInsertId) {
            if (err) {
                res.send(CONSTS.getErrData(err.code));
                return;
            }
            res.send(CONSTS.getErrData('0000'));
        });
});

db.js
var mysql = require('mysql');
var pool = mysql.createPool({
    connectionLimit: 3,
    user: 'xxxx',
    password: 'xxxx',
    database: 'xxxx'
});

var sqlMember = {
     //신규 회원 가입
    "insertNew": "insert into tb_member(user_id, user_name, " +
        "user_password, user_nickname, email, sex, phone, address, mem_type, reg_date) " +
        "values(?, ?, ?, ?, ?, ?, ?, ?, ?, now())",
    // 회원 정보 갱신
    "upateInfo": "update tb_member set user_password = ?, edit_date = now() " +
        "where id = ? ",
    // 회원 정보 조회
    "selectInfo": "select * from tb_member where user_id = ? and mem_type = ?",
    // 회원 정보 삭제
    "deleteMember": "delete from tb_member where user_id = ? and mem_type = ?",
};

MemberProvider = function() {

}

MemberProvider.prototype.insertMember = function (datas, callback) {
    // console.log('insertMember : ' + datas);
    // TODO Password 저장시 암호화 필요함.
    //      암호화 후 로그인에서 비밀번호 체크 로직 보완 필요함.
    pool.getConnection(function (err, conn) {
        conn.query(sqlMember.insertNew, datas, function(err, rows) {
            if (err) {
                conn.release;
                console.error("err : " + err);
                callback(err);
            } else {
                console.log("rows : " + JSON.stringify(rows));
                conn.release;
                callback(null, rows.insertId);
            }
        });
    });
};

수행결과
...
data : mocha02,C
  1) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 성공
    치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패: { user_id: 'mocha02', password: 'a', mem_type: 'C' }
queryID : selectInfo
data : mocha02,C
  2) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패
  3) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 치과병원정보 삭제
  3 passing (15s)
  3 failing

  1) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 성공:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.
 

  2) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 로그인 실패:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.
 

  3) 치과병원 회원 가입,로그인 테스트 -> 치과병원 -> 치과병원정보 삭제:
     Error: timeout of 5000ms exceeded. Ensure the done() callback is being called in this test.

발생원인]

Mocha test case 5번 모두가 비동기로 수행된다.
비동기로 수행된다는 것은 모두 동시에 DB에 접속해서 작업을 수행하게 된다.
이때, 문제는 동시에 5개의 test case 모두 db에 접속하려고 하므로 connectionLimit : 3의 제약에 따라서 대기하게 되므로 Timeout 오류가 발생하는 것이다.
수행결과 화면에 뿌려지는 결과는 수행결과를 그냥 순차적으로 표시하는 것으로 보인다.

이제까지 내가 착각한 부분 Mocha가 순차적으로 하나씩 수행하므로 DB connection Pool 의 connectionLimit에는 영향이 없다고 판단한 착오가 있었다.

해결방법]

동시 수행하는 test case 만큼 db connection pool의 설정에서 limit 개수를 올려준다.
var mysql = require('mysql');
var pool = mysql.createPool({
    connectionLimit: 10,
    user: 'xxxx',
    password: 'xxxx',
    database: 'xxxx'
});


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 22일 수요일

Node.js + Multer + Ajax를 사용한 파일 업로드 예제

Node.js에서 Multer를 이용하여 파일 업로드를 수행하는 예제
Multer는 multipart/form-data 처리를 위한 미들웨어임
1. app.js 에 multer 선언
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var session = require('express-session');
var multer  = require('multer');

var office = require('./routes/office');

var app = express();

// uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(multer());
app.use(cookieParser());
app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
}));
app.use(express.static(path.join(__dirname, 'public')));


2. office.js Route 파일
- file upload를 실행 로직
var CONSTS = require('./consts');
var express = require('express');
var fs = require('fs');

router.post('/info/upload-new-photo', function(req, res, next) {
    console.log(req.body);
    console.log(req.files);
    var imageFile = req.files.uploadnewphoto;
    if (imageFile) {
        // 변수 선언
        var name = imageFile.name;
        var path = imageFile.path;
        var type = imageFile.mimetype;
        // 이미지 파일 확인
        if (type.indexOf('image') != -1) {
            // 이미지 파일의 경우 : 파일 이름을 변경합니다.
            var outputPath = CONSTS.UPLOADPATH + Date.now() + '_' + name;
            fs.rename(path, outputPath, function (err) {
                if (err) {
                    res.send(CONSTS.getErrData(err.code));
                    return;
                }
                res.send(CONSTS.getErrData('0000'));

            });
        } else {
            // 이미지 파일이 아닌 경우 : 파일 이름 제거
            fs.unlink(path, function(err) {
                res.send(CONSTS.getErrData('E004'));
            });
        }
    } else {
        res.send(CONSTS.getErrData('E003'));
    }
});

3. basic.jade 파일
- 파일 업로드용 client 파일
- Ajax로 파일을 전송한다.
    script(type="text/javascript").
        $(document).ready(function() {
            $("#photo-update-action-selector").click(function() {
                $("#photo-update-action-selector").children(".select-items").toggle();
            });

            $("#upload-new-photo").click(function() {
                $("#photo-upload-error-box").html("");
                $("#uploadnewphoto").click();
            });

            $("#remove-photo").click(function() {

            });

            $("#uploadnewphoto").change(function() {
                var file = this.files[0];

                if (checkUploadFile(file, 700) == true) {
                    var formData = new FormData($('#frmOffice')[0]);
                    //- formData.append('file', $("#uploadnewphoto")[0].files[0]);

                    $.ajax({
                        url: "/office/info/upload-new-photo",
                        type: "POST",
                        processData: false,
                        contentType: false,
                        data: formData,
                        success: function(result) {
                            if (result.code == "0000") {
                                alert('success');
                                //- location.href = "/home#{logintype}";
                            } else {
                                $("#photo-upload-error-box").html(result.message);
                            }
                        },
                        error: function(req, status, err) {
                            //- alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error);
                            $("#errormsg").html("code:"+status+"\n"+"message:"+req.responseText+"\n"+"error:"+err);
                        }
                    });
                }
            });
        });

        form#frmOffice(name="frmOffice", enctype="multipart/form-data")
            div.row-item
                fieldset
                    legend.row-title  로고
                    div.settings-item
                        div#photo
                            div
                                div
                                    div.photo-preview.left
                                        img#photo-upload-preview(src="/images/no-profile-pic.png")
                                    div.upload-action-selector.left
                                        div#photo-update-action-selector.select-container
                                            div.select-arrow
                                            div.selected-item 이미지 변경
                                            ul.select-items(style="display:none")
                                                li#upload-new-photo.select-enabled.select-hovering-item 새 이미지 업로드
                                                li#remove-photo.select-enabled 제거
                                        div#photo-upload-error-box.error-message
                                        div.accepted-photo-types .jpg, .gif, .png. 최대 파일 크기는 700K입니다.
                                        div
                                            input#uploadnewphoto(type="file" name="uploadnewphoto")


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Node.js + Mocha 테스트] 로그인 후(세션 생성) 파일 Upload 수행 테스트

Mocha 이용한 테스트로써 먼저 로그인을 수행해서 사용자 세션을 만든 후 파일 업로드를 수행하는 예제

1. testOfficeFile.js
- cookie를 이용하여 로그인시 생성되는 세션정보 설정
- 파일 업로드시 set('cookie', cookie)로 세션정보 set
- attach로 첨부파일 전송
var should = require('should');
var assert = require("assert")
var request = require("supertest");
var expect = require("chai").expect;
var server = require("../app.js");

describe("테스트 ->", function() {
    var svr = "http://localhost:4000";
    var user_id = 'bbb';
    var password = 'bbb';
    var password_wrong = 'a';
    var mem_type = 'C';
    var cookie;

    before(function() {
        server.listen(4000);
    });

    describe("테스트 ->", function() {
        // 로그인 실행
        it('login', loginOffice());
        // 이미지 upload
        it ("이미지 파일 Upload", function(done) {
            var data = {
                user_id: user_id,
                password : password,
                user_name : '김치과',
                email : '',
                phone : '',
                cellphone : '',
                sex : '',
                logintype : mem_type
            };
            request(svr)
                .post("/office/info/upload-new-photo")
                .set('cookie', cookie)
                .field('Content-Type', 'multipart/form-data')
                .send(data)
                .attach('uploadnewphoto', '/Users/baesunghan/Downloads/testlogo.jpg')
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    console.log(res.body.code);
                    console.log(res.body.message);
                    expect('0000').to.equal(res.body.code);
                    done();
                });
        });
    });

    function loginOffice() {
        return function(done) {
            var data = {
                user_id: user_id,
                password : password,
                mem_type : mem_type
            };
            request(svr)
                .post("/member/login")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    res.body.code.should.be.equal('0000');
                    // 로그인 성공후 세션 정보를 cookie에 설정
                    cookie = res.headers['set-cookie'];
                    done();
                });
        }
    }
});


2. office.js Route 파일
- file upload를 실행 로직
var CONSTS = require('./consts');
var express = require('express');
var fs = require('fs');

router.post('/info/upload-new-photo', function(req, res, next) {
    console.log(req.body);
    console.log(req.files);
    var imageFile = req.files.uploadnewphoto;
    if (imageFile) {
        // 변수 선언
        var name = imageFile.name;
        var path = imageFile.path;
        var type = imageFile.mimetype;
        // 이미지 파일 확인
        if (type.indexOf('image') != -1) {
            // 이미지 파일의 경우 : 파일 이름을 변경합니다.
            var outputPath = CONSTS.UPLOADPATH + Date.now() + '_' + name;
            fs.rename(path, outputPath, function (err) {
                if (err) {
                    res.send(CONSTS.getErrData(err.code));
                    return;
                }
                res.send(CONSTS.getErrData('0000'));

            });
        } else {
            // 이미지 파일이 아닌 경우 : 파일 이름 제거
            fs.unlink(path, function(err) {
                res.send(CONSTS.getErrData('E004'));
            });
        }
    } else {
        res.send(CONSTS.getErrData('E003'));
    }
});

3. 실행
baesunghan:~/git/dentaljobs$mocha ./test/testOfficeFile

    테스트 -> 테스트 -> login: { user_id: 'bbb', password: 'bbb', mem_type: 'C' }
{ code: '0000', message: 'SUCCESS!!!' }
POST /member/login 200 44.848 ms - 38
  ․ 테스트 -> 테스트 -> login: 66ms
    테스트 -> 테스트 -> 이미지 파일 Upload: session이 있음.
start /office/info/upload-new-photo
{ 'Content-Type': 'multipart/form-data' }
{ uploadnewphoto:
   { fieldname: 'uploadnewphoto',
     originalname: 'testlogo.jpg',
     name: 'e3bb6d90e617db3717bba6174aaddaaa.jpg',
     encoding: '7bit',
     mimetype: 'image/jpeg',
     path: '/var/folders/0y/124f1qys05q4gvfgs1f4bg2h0000gn/T/e3bb6d90e617db3717bba6174aaddaaa.jpg',
     extension: 'jpg',
     size: 2208,
     truncated: false,
     buffer: null } }
POST /office/info/upload-new-photo
200 17.854 ms - 38
0000
SUCCESS!!!
  ․ 테스트 -> 테스트 -> 이미지 파일 Upload: 32ms

  2 passing (111ms)
baesunghan:~/git/dentaljobs$

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 17일 금요일

민주주의의 재발견(박상훈)을 읽고서


민주주의는 정당정치이고 이 정당은 당원들의 의견에 부합해서 정치를 해야한다고 한다. 
내가 이전까지 생각하던 모든 국민을 위한 정치를 해야 올바른 정당이라고 생각했었는데
그런 일은 있을수 없고 또한 그건 정당정치로써의 민주주의가 아니다라고 말한다. 

이를 적용해보면 현 상황에 적용해자.
새누리당은 보수로서 좀 있는, 많이 가진사람들을 대변하고 있고 그들의 의견을 대변해서 관철시키고 있으므로 잘 하고 있는 것이고, 새정치민주연합은 좀 덜 가지고 있는 사람들의 의견을 대변해서 제대로 관철시키지 못하므로 잘 하고 있지 못하는 것이다.
또한 마찬가지로 진보정의당, 민노당도 마찬가지...

그러나 여기서 정치가 공평하지 않는 상황에서 치러지고 있는 현실을 감안하지 않고 있다. 
대부분의 사람들이 접하는 매체, 즉 방송, 신문등이 새누리, 정부의 나팔수인 상황에서 대다수의 사람들은 새누리의 여러가지를 정책을 오인하게 만든다.
분명히 많을 논의가 필요한 많은 상황에서도, 다수를 통한 일방적인 밀어붙이기식을 해도 지지층에서는 별다른 문제제기가 없는 듯 하다.
이건 아니라고 본다.

가장 아쉬운 점은 어떤 이유에서인지 민주주의 정당의 한 축인 새누리나 다른 소수 정당에 대한 작가의 의견이나 평가가 전혀 없다.
작가는 새누리나 정의당, 민노당에 대해서는 어떻게 판단하는지도 같이 적었다면 더 좋았을듯 한데 아쉽다.

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 14일 화요일

Node.js + Express 4] session sample


Express 4 에서는 3에서와는 다르게 session을 사용함
로그인시 세션에 사용자 정보를 설정하여 다시 로그인 호출시 로그인 성공 화면으로 넘어가도록 구현한 샘플

1. 설치
sudo npm install express-session

2. 사용
- app.js
var express = require('express');
var session = require('express-session');

var routes = require('./routes/index');
var users = require('./routes/users');
var member = require('./routes/member');
var personal = require('./routes/personal');
var office = require('./routes/office');

var app = express();

app.use(session({
  secret: 'keyboard cat',
  resave: false,
  saveUninitialized: true,
}));

app.use('/', routes);
app.use('/users', users);
app.use('/member', member);
app.use('/personal', personal);
app.use('/office', office);


- member.js
var CONSTS = require('./consts');
var express = require('express');
var router = express.Router();
var MemberProvider = require('./db/db-member.js').MemberProvider;

var app = express();
var memberProvider = new MemberProvider();

// 로그인 화면 호출
router.get('/loginc', function(req, res, next) {
    // 이미 로그인 여부를 세션에 user_id가 있는지로 확인
    var userInfo = req.session.userInfo;
    var user_id = '';
    try {
        user_id = userInfo.user_id;
    } catch (e) { }
    console.log('member - session : ' + userInfo);
    if (user_id == '') {
        res.render('./member/login', {
            title: '로그인',
            logintype: CONSTS.MEMBER_TYPE.OFFICE});
    } else {
        res.redirect('/home' + userInfo.mem_type);
    }
});

// 로그인 실행
router.post('/login', function(req, res, next) {
    console.log(req.body);
    var vId = req.body.user_id;
    var vPwd = req.body.password;
    var datas = [vId, req.body.mem_type];
    memberProvider.selectSingleQueryByID("selectInfo", datas, function(err, memberInfo) {
        console.log(memberInfo);
        var rtnDatas = CONSTS.getErrData('0000');
        if (memberInfo == null) {
            rtnDatas = CONSTS.getErrData('E001');
        } else if (memberInfo.user_password != vPwd) {
            rtnDatas = CONSTS.getErrData('E002');

        } else {
            // 세션에 값을 설정함.
            var userInfo = {
                user_id: memberInfo.user_id,
                mem_type: memberInfo.mem_type};
            req.session.userInfo = userInfo;
        }
        console.log(rtnDatas);
        res.send(rtnDatas);
    });
});

// 로그아웃
router.get('/logout', function(req, res, next) {
    // 세션을 삭제함.
    req.session.destroy(function(err) {
        res.redirect('/');
    });
});


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 3일 금요일

삐꾸란 말이 순우리말이네요.

"니 눈이 삐꾸냐!!!! 그것도 못 맞추게...."

어릴때 많이 썼던 "삐꾸"라는 말.
일본말인줄 알았는데, 순 우리말이라네요.

참고로, 한자어인 `비구(非俱)` 에서 음이 바뀐 은어입니다. 



한국어 사전 

2015년 7월 2일 목요일

Node.js] Mocha를 이용하여 테스트 모듈 구성


Node.js 프로젝트에서 테스트를 위해서 Mocha Framework 적용
추가로 supertest, should, expect 등을 설치함.

1. 설치
- Mocha는 Global Module이므로 관리자 권한으로 설치함.
baesunghan:~/git/dentalJobs$sudo npm install -g mocha
Password:
/usr/local/bin/mocha -> /usr/local/lib/node_modules/mocha/bin/mocha
/usr/local/bin/_mocha -> /usr/local/lib/node_modules/mocha/bin/_mocha
mocha@2.2.5 /usr/local/lib/node_modules/mocha
├── escape-string-regexp@1.0.2
├── supports-color@1.2.1
├── growl@1.8.1
├── commander@2.3.0
├── diff@1.4.0
├── debug@2.0.0 (ms@0.6.2)
├── jade@0.26.3 (commander@0.6.1, mkdirp@0.3.0)
├── mkdirp@0.5.0 (minimist@0.0.8)
└── glob@3.2.3 (inherits@2.0.1, graceful-fs@2.0.3, minimatch@0.2.14)
baesunghan:~/git/dentalJobs$

일반계정으로 추가로 다음 모듈도 설치함.
baesunghan:~/git/dentalJobs$npm install supertest
baesunghan:~/git/dentalJobs$npm install should
baesunghan:~/git/dentalJobs$npm install expect


2. test dir 생성
- mocha 실행시 test dir 아래 테스트 모듈을 실행하게 됨
baesunghan:~/git/dentalJobs$mkdir test
baesunghan:~/git/dentalJobs$ll
total 24
drwxr-xr-x  13 baesunghan  staff   442  7  1 16:51 .
drwxr-xr-x  18 baesunghan  staff   612  6 22 09:31 ..
drwxr-xr-x  13 baesunghan  staff   442  7  1 13:55 .git
-rw-r--r--   1 baesunghan  staff    12  6 29 00:07 .gitignore
-rw-r--r--   1 baesunghan  staff  1576  6  9 16:21 app.js
drwxr-xr-x   3 baesunghan  staff   102  6  3 14:03 bin
drwxr-xr-x  13 baesunghan  staff   442  6  3 16:03 node_modules
-rw-r--r--   1 baesunghan  staff   376  6  3 18:47 package.json
drwxr-xr-x   5 baesunghan  staff   170  6  3 14:03 public
drwxr-xr-x   8 baesunghan  staff   272  7  1 16:21 routes
drwxr-xr-x   2 baesunghan  staff    68  7  1 16:51 test
-rw-r--r--   1 baesunghan  staff     0  6  8 10:34 todolist.TODO
drwxr-xr-x  13 baesunghan  staff   442  6 16 18:25 views
baesunghan:~/git/dentalJobs$

3. mocha.opts 생성
- mocha 실행을 위한 기본 환경 설정 파일
--require should
--require supertest
--reporter list
--ui bdd

4. app.js
- 이미 만들어진 app 모듈
- express 로 만들어짐
- route에서 member.js를 require함
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var routes = require('./routes/index');
var users = require('./routes/users');
var member = require('./routes/member');
var personal = require('./routes/personal');

var app = express();

5. member.js
- 로그인을 수행하는 로직
- client로 return 메시지는 {code:'0000', message:'SUCCESS!!!!'} or {code:'E002', message:'비밀번호가 틀립니다'} 등으로 구성됨
var CONSTS = require('./consts');
var express = require('express');
var router = express.Router();
var MemberProvider = require('./db/db-member.js').MemberProvider;

var memberProvider = new MemberProvider();

// 로그인 
router.post('/loginc', function(req, res, next) {
    console.log(req.body);
    var vId = req.body.user_id;
    var vPwd = req.body.password;
    var datas = [vId, req.body.mem_type];
    memberProvider.selectSingleQueryByID("selectInfo", datas, function(err, memberInfo) {
        console.log(memberInfo);
        var rtnDatas = CONSTS.getErrData('0000');
        if (memberInfo == null) {
            rtnDatas = CONSTS.getErrData('E001');
        } else if (memberInfo.user_password != vPwd) {
            rtnDatas = CONSTS.getErrData('E002');

        }
        console.log(rtnDatas);
        res.send(rtnDatas);
    });
});



6. consts.js
- 공통 상수/에러코드 정의
function define(name, value) {
    Object.defineProperty(exports, name, {
        value: value,
        enumerable : true,
    });
}

define("OFFICE_TYPE", {"PERSONAL": "P", "TOTAL":"T", "GOVERNMENT":"G"});
define("MEMBER_TYPE", {"PERSONAL": "B", "OFFICE": "C"});

define("ERROR_CODE", {
    "0000": "SUCCESS!!!",
    "E001": "일치하는 ID가 없습니다.",
    "E002": "비밀번호가 틀립니다.",
    "D001": "동일한 #1 #2가 이미 존재합니다.",
    "D002": "DB에 접속할 수 없습니다. 시스템관리자에게 연락하세요.",
    "ER_DUP_ENTRY": "Duplicate entry for key 'PRIMARY'!!!",
    "ER_BAD_NULL_ERROR": "Column 'user_id' cannot be null",
});

function getErrData(key) {
    console.log(key);
    return {code: key, message : exports.ERROR_CODE[key]};
}

exports.getErrData = getErrData;

7. test.js
- Mocha 테스트 모듈
- supertest, expect, should를 모두 테스트 용으로 사용함.
- http 호출은 Async 모드로 수행해함. 그러므로 it('로그인 성공', function(done) 로 해야함.
var should = require('should');
var assert = require("assert")
var request = require("supertest");
var expect = require("chai").expect;
var server = require("../app.js");

describe("회원 가입,로그인 테스트 ->", function() {
    var svr = "http://localhost:4000";

    before(function() {
        server.listen(4000);
    });
    describe("치위생사 ->", function() {
        it ("로그인 성공", function(done) {
            var data = {
                user_id : "e",
                password : "e", 
                mem_type : 'C'
            };

            request(svr)
                .post("/member/loginc")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    res.body.code.should.be.equal('0000');
                    done();
                });
        });

        it ("로그인 실패", function(done) {
            var data = {
                user_id : "e",
                password : "ea", 
                mem_type : 'C'
            };

            request(svr)
                .post("/member/loginc")
                .send(data)
                .expect(200)
                .end(function(err, res) {
                    if (err) return done(err);
                    Expect('E002').to.equal(res.body.code);
                    done();
                });
        });

    after(function() {
        // server.close();
    });
});

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

2015년 7월 1일 수요일

Node.js, Express, JQuery에서 Ajax 호출 예제

Post 방식 예제임

1. routing을 하는 member.js 정의
- loginb 호출시 DB에서 사용자 정보 존재 여부 조회 후 메시지를 json으로 return함
var MemberProvider = require('./db/db-member.js').MemberProvider;

var memberProvider = new MemberProvider();

// 로그인 실행 
router.post('/loginb', function(req, res, next) {
    var vId = req.body.email;
    var vPwd = req.body.user_password;
    var datas = [vId, req.body.mem_type];
    // DB에서 사용자 정보 조회
    memberProvider.selectSingleQueryByID("selectInfo", datas, function(err, memberInfo) {
        var vMsg = "";
        if (memberInfo == null) {
            vMsg = "일치하는 ID가 없습니다. ";
        } else if (memberInfo.password != vPwd) {
            vMsg = "비밀번호가 틀립니다.";
        } 

        // 로그인 성공
        if (vMsg == "") {
            res.render('./home', {
                title: 'Main',
                logintype: CONSTS.MEMBER_TYPE.PERSONAL,
                item: memberInfo,
                message: "환영합니다."});
        } else {    // 로그인 실패
            res.render('./member/login', {
                title: 'login failed',
                logintype: CONSTS.MEMBER_TYPE.PERSONAL,
                message: vMsg});
        }
    });
});

2. login view용 jade 파일
- view 구성을 담당
- 두가지 방식(1. $.ajax, 2. $.post)로 Ajax 호출을 수행할 수 있음
- $.ajax는 요청 오류에 대해서도 대응이 가능함
extends ../layout-intro

block scripts
    script(type="text/javascript").
        $(document).ready(function() {
            $("#login").click(function() {
                if ($("#email").val() == "") {
                    alert('이메일(아이디)를 입력하세요.');
                    $("#email").focus();
                    return false;
                }
                if ($("#password").val() == "") {
                    alert('비밀번호를 입력하세요.');
                    $("#password").focus();
                    return false;
                }

                // 1. $.ajax이용
                $.ajax({
                    url: "/member/login#{logintype}",
                    dataType: "json",
                    type: "POST",
                    data:$('#login_account').serialize(),
                    success: function(data) {
                        alert('callback : ' + data.message);
                    },
                    error: function() {
                        alert('error');
                    }
                })
                
                // 2. $.ajax의 약식인 $.post 이용
                //- $.post("/member/login#{logintype}", {email:'a', pasword:'b'}, function(data) {
                //-     alert(data.message);
                //- });
            });
        });

block content
    section.login-sect
        div.heading
            div.branding
            h1= title
        div.login-body
            div.row-item
                form#login_account.login-form(action="/member/login#{logintype}" method="post")
                    input(type="hidden", name="mem_type", value="#{logintype}")
                    ol
                        li.row-item
                            label(for="email-addres") 이메일
                            div.input-wrapper
                                input#email.TextInput.TextInput_Large(type="email", name="email" placeholder="이메일 주소" value="#{userid}")
                        li.row-item
                            label(for="password") 비밀번호
                            div.input-wrapper
                                input#password.TextInput.TextInput_Large(type="password", name="password" placeholder="비밀번호")
                        li
                            input#login.Btn.Btn_emph.Btn_super(name="login", type="button" value="로그인")
            div.row-item
                p.already 아직 계정이 없으신가요?
                a.change-form(href="/member/register#{logintype}") 회원가입 


3. layout-intro.jade 
- view용 사용되는 template jade 파일
doctype html
html
    head
        title= title
        link(rel='stylesheet', href='/stylesheets/style.css')
        script(src="/javascripts/jquery-1.11.2.min.js")
        block scripts
    body
            block content
    footer
        nav.footer-nav
            div.row


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Node.js + Express ) 공통 상수(Common Constants/Message) 정의

Node.js + Express 프로젝트내에서 사용할 공통 상수 정의를 consts.js 파일에 정의해서 사용함.
Message, Label, Error Message 등도 동일한 방법으로 정의해서 사용할 수 있음

1. consts.js 
- 일반적인 공통 상수를 정의하는 js
function define(name, value) {
    Object.defineProperty(exports, name, {
        value: value,
        enumerable : true,
    });
}

define("OFFICE_TYPE", {"PERSONAL": "P", "TOTAL":"T", "GOVERNMENT":"G"});
define("MEMBER_TYPE", {"PERSONAL": "B", "OFFICE": "C"});


2. member.js
- 공통 상수 정의를 사용하는 route js
var CONSTS = require('./consts');

router.get('/loginb', function(req, res, next) {
    res.render('./member/login', {
        logintype: CONSTS.MEMBER_TYPE.PERSONAL});
});


이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.