@@ -3,6 +3,7 @@ package deployments_test
33import (
44 "bytes"
55 "encoding/json"
6+ "errors"
67 "fmt"
78 "io"
89 "io/ioutil"
@@ -939,6 +940,226 @@ var _ = Describe("Deployments", func() {
939940 })
940941 })
941942
943+ Describe ("GET /projects/:project_name/deployments/:id/download" , func () {
944+ var (
945+ err error
946+
947+ fakeS3 * fake.S3
948+ origS3 filetransfer.FileTransfer
949+
950+ u * user.User
951+ t * oauthtoken.OauthToken
952+
953+ headers http.Header
954+ proj * project.Project
955+ depl * deployment.Deployment
956+ bun * rawbundle.RawBundle
957+ )
958+
959+ BeforeEach (func () {
960+ origS3 = s3client .S3
961+ fakeS3 = & fake.S3 {}
962+ s3client .S3 = fakeS3
963+
964+ u , _ , t = factories .AuthTrio (db )
965+
966+ headers = http.Header {
967+ "Authorization" : {"Bearer " + t .Token },
968+ }
969+
970+ proj = & project.Project {
971+ Name : "foo-bar-express" ,
972+ UserID : u .ID ,
973+ }
974+ Expect (db .Create (proj ).Error ).To (BeNil ())
975+
976+ bun = factories .RawBundle (db , proj )
977+
978+ depl = factories .DeploymentWithAttrs (db , proj , u , deployment.Deployment {
979+ Prefix : "a1b2c3" ,
980+ State : deployment .StateDeployed ,
981+ DeployedAt : timeAgo (- 1 * time .Hour ),
982+ RawBundleID : & bun .ID ,
983+ })
984+ })
985+
986+ AfterEach (func () {
987+ s3client .S3 = origS3
988+ })
989+
990+ doRequest := func () {
991+ s = httptest .NewServer (server .New ())
992+ uri := fmt .Sprintf ("%s/projects/foo-bar-express/deployments/%d/download" , s .URL , depl .ID )
993+
994+ req , err := http .NewRequest ("GET" , uri , nil )
995+ Expect (err ).To (BeNil ())
996+
997+ if headers != nil {
998+ for k , v := range headers {
999+ for _ , h := range v {
1000+ req .Header .Add (k , h )
1001+ }
1002+ }
1003+ }
1004+
1005+ // Hack so that we don't follow redirects.
1006+ // In Go 1.7, we can use http.ErrUseLastResponse (see https://github.com/golang/go/commit/8f13080267d0ddbb50da9029339796841224116a).
1007+ redirectErr := errors .New ("redirect received" )
1008+ client := & http.Client {
1009+ CheckRedirect : func (req * http.Request , via []* http.Request ) error {
1010+ if len (via ) > 0 {
1011+ return redirectErr
1012+ }
1013+ return nil
1014+ },
1015+ }
1016+
1017+ res , err = client .Do (req )
1018+ if err != nil {
1019+ if e , ok := err .(* url.Error ); ok {
1020+ Expect (e .Err ).To (Equal (redirectErr ))
1021+ return
1022+ }
1023+ }
1024+
1025+ Expect (err ).To (BeNil ())
1026+ }
1027+
1028+ sharedexamples .ItRequiresAuthentication (func () (* gorm.DB , * user.User , * http.Header ) {
1029+ return db , u , & headers
1030+ }, func () * http.Response {
1031+ doRequest ()
1032+ return res
1033+ }, nil )
1034+
1035+ sharedexamples .ItRequiresProjectCollab (func () (* gorm.DB , * user.User , * project.Project ) {
1036+ return db , u , proj
1037+ }, func () * http.Response {
1038+ doRequest ()
1039+ return res
1040+ }, nil )
1041+
1042+ Context ("when the deployment does not exist" , func () {
1043+ BeforeEach (func () {
1044+ Expect (db .Delete (depl ).Error ).To (BeNil ())
1045+ })
1046+
1047+ It ("responds with 404 Not Found" , func () {
1048+ doRequest ()
1049+
1050+ b := & bytes.Buffer {}
1051+ _ , err = b .ReadFrom (res .Body )
1052+
1053+ Expect (res .StatusCode ).To (Equal (http .StatusNotFound ))
1054+ Expect (b .String ()).To (MatchJSON (`{
1055+ "error": "not_found",
1056+ "error_description": "deployment could not be found"
1057+ }` ))
1058+ })
1059+ })
1060+
1061+ Context ("when the deployment id is not a number" , func () {
1062+ BeforeEach (func () {
1063+ s = httptest .NewServer (server .New ())
1064+ url := fmt .Sprintf ("%s/projects/foo-bar-express/deployments/cafebabe" , s .URL )
1065+ res , err = testhelper .MakeRequest ("GET" , url , nil , headers , nil )
1066+ Expect (err ).To (BeNil ())
1067+ })
1068+
1069+ It ("responds with 404 Not Found" , func () {
1070+ b := & bytes.Buffer {}
1071+ _ , err = b .ReadFrom (res .Body )
1072+
1073+ Expect (res .StatusCode ).To (Equal (http .StatusNotFound ))
1074+ Expect (b .String ()).To (MatchJSON (`{
1075+ "error": "not_found",
1076+ "error_description": "deployment could not be found"
1077+ }` ))
1078+ })
1079+ })
1080+
1081+ Context ("when the deployment does not have an associated RawBundle" , func () {
1082+ BeforeEach (func () {
1083+ depl .RawBundleID = nil
1084+ Expect (db .Save (depl ).Error ).To (BeNil ())
1085+ })
1086+
1087+ It ("responds with 404 Not Found" , func () {
1088+ doRequest ()
1089+
1090+ b := & bytes.Buffer {}
1091+ _ , err = b .ReadFrom (res .Body )
1092+
1093+ Expect (res .StatusCode ).To (Equal (http .StatusNotFound ))
1094+ Expect (b .String ()).To (MatchJSON (`{
1095+ "error": "not_found",
1096+ "error_description": "deployment cannot be downloaded"
1097+ }` ))
1098+ })
1099+ })
1100+
1101+ Context ("when the deployment's associated RawBundle does not exist" , func () {
1102+ BeforeEach (func () {
1103+ Expect (db .Delete (bun ).Error ).To (BeNil ())
1104+ })
1105+
1106+ It ("responds with 410 Gone" , func () {
1107+ doRequest ()
1108+
1109+ b := & bytes.Buffer {}
1110+ _ , err = b .ReadFrom (res .Body )
1111+
1112+ Expect (res .StatusCode ).To (Equal (http .StatusGone ))
1113+ Expect (b .String ()).To (MatchJSON (`{
1114+ "error": "gone",
1115+ "error_description": "deployment can no longer be downloaded"
1116+ }` ))
1117+ })
1118+ })
1119+
1120+ Context ("when raw bundle does not exist on S3" , func () {
1121+ BeforeEach (func () {
1122+ fakeS3 .ExistsReturn = false
1123+ })
1124+
1125+ It ("responds with 410 Gone" , func () {
1126+ doRequest ()
1127+
1128+ b := & bytes.Buffer {}
1129+ _ , err = b .ReadFrom (res .Body )
1130+
1131+ Expect (res .StatusCode ).To (Equal (http .StatusGone ))
1132+ Expect (b .String ()).To (MatchJSON (`{
1133+ "error": "gone",
1134+ "error_description": "deployment can no longer be downloaded"
1135+ }` ))
1136+
1137+ Expect (fakeS3 .ExistsCalls .Count ()).To (Equal (1 ))
1138+ })
1139+ })
1140+
1141+ Context ("when raw bundle exists on S3" , func () {
1142+ BeforeEach (func () {
1143+ fakeS3 .ExistsReturn = true
1144+ fakeS3 .PresignedURLReturn = "https://s3-us-west-2.amazonaws.com/deployments/abcd/raw-bundle.zip?abc=123"
1145+ })
1146+
1147+ It ("redirects to a pre-signed download URL of the raw bundle in S3" , func () {
1148+ doRequest ()
1149+
1150+ Expect (fakeS3 .PresignedURLCalls .Count ()).To (Equal (1 ))
1151+ call := fakeS3 .PresignedURLCalls .NthCall (1 )
1152+ Expect (call ).NotTo (BeNil ())
1153+ Expect (call .Arguments [0 ]).To (Equal (s3client .BucketRegion ))
1154+ Expect (call .Arguments [1 ]).To (Equal (s3client .BucketName ))
1155+ Expect (call .Arguments [2 ]).To (Equal (bun .UploadedPath ))
1156+
1157+ Expect (res .StatusCode ).To (Equal (http .StatusFound ))
1158+ Expect (res .Header .Get ("Location" )).To (Equal (fakeS3 .PresignedURLReturn ))
1159+ })
1160+ })
1161+ })
1162+
9421163 Describe ("POST /projects/:project_name/rollback" , func () {
9431164 var (
9441165 err error
0 commit comments