Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
nomad-lab
nomad-FAIR
Commits
2b8429c2
Commit
2b8429c2
authored
Aug 27, 2019
by
Markus Scheidgen
Browse files
Misc fixes and improvements.
parent
0e2e33e4
Changes
8
Hide whitespace changes
Inline
Side-by-side
gui/src/components/api.js
View file @
2b8429c2
...
...
@@ -59,6 +59,7 @@ class Upload {
uploadFile
(
file
)
{
const
uploadFileWithProgress
=
async
()
=>
{
const
authHeaders
=
await
this
.
api
.
authHeaders
()
let
uploadRequest
=
await
UploadRequest
(
{
request
:
{
...
...
@@ -66,7 +67,7 @@ class Upload {
method
:
'
PUT
'
,
headers
:
{
'
Content-Type
'
:
'
application/gzip
'
,
...
this
.
api
.
authHeaders
...
authHeaders
}
},
files
:
[
file
],
...
...
@@ -76,7 +77,7 @@ class Upload {
}
)
if
(
uploadRequest
.
error
)
{
handleApiError
(
uploadRequest
.
error
)
handleApiError
(
uploadRequest
.
response
?
uploadRequest
.
response
.
message
:
uploadRequest
.
error
)
}
if
(
uploadRequest
.
aborted
)
{
throw
Error
(
'
User abort
'
)
...
...
@@ -95,7 +96,7 @@ class Upload {
return
new
Promise
(
resolve
=>
resolve
(
this
))
}
else
{
if
(
this
.
upload_id
)
{
return
this
.
api
.
swagger
Promise
.
then
(
client
=>
client
.
apis
.
uploads
.
get_upload
({
return
this
.
api
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
get_upload
({
upload_id
:
this
.
upload_id
,
page
:
page
||
1
,
per_page
:
perPage
||
5
,
...
...
@@ -143,31 +144,48 @@ function handleApiError(e) {
}
class
Api
{
static
async
createSwaggerClient
(
accessToken
)
{
let
data
if
(
accessToken
)
{
let
auth
=
{
'
OpenIDConnect Bearer Token
'
:
`Bearer
${
accessToken
}
`
}
data
=
{
authorizations
:
auth
}
}
swagger
()
{
const
self
=
this
return
new
Promise
((
resolve
,
reject
)
=>
{
self
.
keycloak
.
updateToken
()
.
success
(()
=>
{
self
.
_swaggerClient
.
then
(
swaggerClient
=>
{
swaggerClient
.
authorizations
=
{
'
OpenIDConnect Bearer Token
'
:
`Bearer
${
self
.
keycloak
.
token
}
`
}
resolve
(
swaggerClient
)
})
.
catch
(()
=>
{
reject
(
new
ApiError
())
})
})
.
error
(()
=>
{
reject
(
new
ApiError
())
})
})
}
try
{
return
await
Swagger
(
`
${
apiBase
}
/swagger.json`
,
data
)
}
catch
(
e
)
{
throw
new
ApiError
()
}
authHeaders
()
{
return
new
Promise
((
resolve
,
reject
)
=>
{
this
.
keycloak
.
updateToken
()
.
success
(()
=>
{
resolve
({
'
Authorization
'
:
`Bearer
${
this
.
keycloak
.
token
}
`
})
})
.
error
(()
=>
{
reject
(
new
ApiError
())
})
})
}
constructor
(
accessToken
)
{
constructor
(
keycloak
)
{
this
.
onStartLoading
=
()
=>
null
this
.
onFinishLoading
=
()
=>
null
this
.
authHeaders
=
{
'
Authentication
'
:
`Bearer
${
accessToken
}
`
}
this
.
swaggerPromise
=
Api
.
createSwaggerClient
(
accessToken
).
catch
(
handleApiError
)
this
.
_swaggerClient
=
Swagger
(
`
${
apiBase
}
/swagger.json`
)
this
.
keycloak
=
keycloak
// keep a list of localUploads, these are uploads that are currently uploaded through
// the browser and that therefore not yet returned by the backend
...
...
@@ -188,7 +206,7 @@ class Api {
async
getUnpublishedUploads
()
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
get_uploads
({
state
:
'
unpublished
'
,
page
:
1
,
per_page
:
1000
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
({
...
...
@@ -204,7 +222,7 @@ class Api {
async
getPublishedUploads
(
page
,
perPage
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
get_uploads
({
state
:
'
published
'
,
page
:
page
||
1
,
per_page
:
perPage
||
10
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
({
...
...
@@ -220,7 +238,7 @@ class Api {
async
archive
(
uploadId
,
calcId
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
archive
.
get_archive_calc
({
upload_id
:
uploadId
,
calc_id
:
calcId
...
...
@@ -247,7 +265,7 @@ class Api {
async
calcProcLog
(
uploadId
,
calcId
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
archive
.
get_archive_logs
({
upload_id
:
uploadId
,
calc_id
:
calcId
...
...
@@ -259,7 +277,7 @@ class Api {
async
getRawFileListFromCalc
(
uploadId
,
calcId
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
raw
.
get_file_list_from_calc
({
upload_id
:
uploadId
,
calc_id
:
calcId
,
...
...
@@ -272,7 +290,7 @@ class Api {
async
repo
(
uploadId
,
calcId
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
repo
.
get_repo_calc
({
upload_id
:
uploadId
,
calc_id
:
calcId
...
...
@@ -284,7 +302,7 @@ class Api {
async
search
(
search
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
repo
.
search
(
search
))
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
...
...
@@ -293,7 +311,7 @@ class Api {
async
deleteUpload
(
uploadId
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
delete_upload
({
upload_id
:
uploadId
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
...
...
@@ -302,7 +320,7 @@ class Api {
async
publishUpload
(
uploadId
,
withEmbargo
)
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
exec_upload_operation
({
upload_id
:
uploadId
,
payload
:
{
...
...
@@ -319,10 +337,10 @@ class Api {
async
getSignatureToken
()
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
.
then
(
client
=>
client
.
apis
.
auth
.
get_
token
())
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
auth
.
get_
auth
())
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
.
then
(
response
=>
response
.
body
.
signature_token
)
.
finally
(
this
.
onFinishLoading
)
}
...
...
@@ -339,7 +357,7 @@ class Api {
this
.
onStartLoading
()
try
{
const
loadMetaInfo
=
async
(
path
)
=>
{
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
archive
.
get_metainfo
({
metainfo_package_name
:
path
}))
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
...
...
@@ -360,7 +378,7 @@ class Api {
async
getInfo
()
{
if
(
!
this
.
_cachedInfo
)
{
this
.
onStartLoading
()
this
.
_cachedInfo
=
await
this
.
swagger
Promise
this
.
_cachedInfo
=
await
this
.
swagger
()
.
then
(
client
=>
{
return
client
.
apis
.
info
.
get_info
()
.
then
(
response
=>
response
.
body
)
...
...
@@ -373,7 +391,7 @@ class Api {
async
getUploadCommand
()
{
this
.
onStartLoading
()
return
this
.
swagger
Promise
return
this
.
swagger
()
.
then
(
client
=>
client
.
apis
.
uploads
.
get_upload_command
())
.
catch
(
handleApiError
)
.
then
(
response
=>
response
.
body
)
...
...
@@ -392,9 +410,18 @@ export class ApiProviderComponent extends React.Component {
keycloakInitialized
:
PropTypes
.
bool
}
constructor
(
props
)
{
super
(
props
)
this
.
onToken
=
this
.
onToken
.
bind
(
this
)
}
onToken
(
token
)
{
console
.
log
(
token
)
}
update
()
{
const
{
keycloak
}
=
this
.
props
this
.
setState
({
api
:
this
.
createApi
(
keycloak
.
token
)})
this
.
setState
({
api
:
this
.
createApi
(
keycloak
)})
if
(
keycloak
.
token
)
{
keycloak
.
loadUserInfo
()
.
success
(
user
=>
{
...
...
@@ -416,8 +443,8 @@ export class ApiProviderComponent extends React.Component {
}
}
createApi
(
accessToken
)
{
const
api
=
new
Api
(
accessToken
)
createApi
(
keycloak
)
{
const
api
=
new
Api
(
keycloak
)
api
.
onStartLoading
=
(
name
)
=>
{
this
.
setState
(
state
=>
({
loading
:
state
.
loading
+
1
}))
}
...
...
gui/src/components/entry/Download.js
View file @
2b8429c2
...
...
@@ -49,7 +49,7 @@ class Download extends React.Component {
fullUrl
=
`
${
window
.
location
.
origin
}${
fullUrl
}
`
}
const
downloadUrl
=
new
URL
(
fullUrl
)
downloadUrl
.
searchParams
.
append
(
'
token
'
,
result
.
token
)
downloadUrl
.
searchParams
.
append
(
'
signature_
token
'
,
result
)
FileSaver
.
saveAs
(
downloadUrl
.
href
,
fileName
)
this
.
setState
({
preparingDownload
:
false
})
})
...
...
nomad/api/auth.py
View file @
2b8429c2
...
...
@@ -41,7 +41,7 @@ import uuid
from
nomad
import
config
,
processing
,
utils
,
infrastructure
,
datamodel
from
.app
import
api
,
RFC3339DateTime
from
.app
import
api
# Authentication scheme definitions, for swagger
...
...
@@ -129,7 +129,7 @@ def authenticate(
if
token
is
not
None
:
try
:
decoded
=
jwt
.
decode
(
token
,
config
.
services
.
api_secret
,
algorithms
=
[
'HS256'
])
user
=
datamodel
.
User
.
get
(
decoded
[
'user'
])
user
=
datamodel
.
User
(
user_id
=
decoded
[
'user'
]
,
email
=
None
)
if
user
is
None
:
abort
(
401
,
'User for the given signature does not exist'
)
else
:
...
...
@@ -144,12 +144,10 @@ def authenticate(
elif
'token'
in
request
.
args
:
abort
(
401
,
'Queram param token not supported for this endpoint'
)
user_or_error
=
infrastructure
.
keycloak
.
authorize_flask
(
basic
=
basic
)
if
user_or_error
is
not
None
:
if
isinstance
(
user_or_error
,
datamodel
.
User
):
g
.
user
=
user_or_error
else
:
abort
(
401
,
message
=
user_or_error
)
else
:
error
=
infrastructure
.
keycloak
.
authorize_flask
(
basic
=
basic
)
if
error
is
not
None
:
abort
(
401
,
message
=
error
)
if
required
and
g
.
user
is
None
:
abort
(
401
,
message
=
'Authentication is required for this endpoint'
)
...
...
@@ -180,24 +178,7 @@ ns = api.namespace(
description
=
'Authentication related endpoints.'
)
user_model
=
api
.
model
(
'User'
,
{
'user_id'
:
fields
.
String
(
description
=
'The users UUID.'
),
'name'
:
fields
.
String
(
'The publically visible user name.'
),
'first_name'
:
fields
.
String
(
description
=
'The user
\'
s first name'
),
'last_name'
:
fields
.
String
(
description
=
'The user
\'
s last name'
),
'email'
:
fields
.
String
(
description
=
'Guess what, the user
\'
s email'
),
'affiliation'
:
fields
.
Nested
(
model
=
api
.
model
(
'Affiliation'
,
{
'name'
:
fields
.
String
(
description
=
'The name of the affiliation'
,
default
=
'not given'
),
'address'
:
fields
.
String
(
description
=
'The address of the affiliation'
,
default
=
'not given'
)})),
'password'
:
fields
.
String
(
description
=
'The bcrypt 2y-indented password for initial and changed password'
),
'token'
:
fields
.
String
(
description
=
'The access token that authenticates the user with the API. '
'User the HTTP header "X-Token" to provide it in API requests.'
),
'created'
:
RFC3339DateTime
(
description
=
'The create date for the user.'
)
})
auth_model
=
api
.
model
(
'Auth'
,
{
'user'
:
fields
.
Nested
(
user_model
,
skip_none
=
True
,
description
=
'The authenticated user info'
),
'access_token'
:
fields
.
String
(
description
=
'The OIDC access token'
),
'upload_token'
:
fields
.
String
(
description
=
'A short token for human readable upload URLs'
),
'signature_token'
:
fields
.
String
(
description
=
'A short term token to sign URLs'
)
...
...
@@ -211,13 +192,12 @@ class AuthResource(Resource):
@
authenticate
(
required
=
True
,
basic
=
True
)
def
get
(
self
):
"""
Provides
user and
authentication information. This endpoint requires authentification.
Provides authentication information. This endpoint requires authentification.
Like all endpoints the OIDC access token based authentification. In additional,
basic HTTP authentification can be used. This allows to login and acquire an
access token.
The response contains information about the authentificated user; a
a short (10s) term JWT token that can be used to sign
The response contains a short (10s) term JWT token that can be used to sign
URLs with a ``signature_token`` query parameter, e.g. for file downloads on the
raw or archive api endpoints; a short ``upload_token`` that is used in
``curl`` command line based uploads; and the OIDC JWT access token.
...
...
@@ -231,7 +211,6 @@ class AuthResource(Resource):
try
:
return
{
'user'
:
infrastructure
.
keycloak
.
get_user
(
g
.
user
.
user_id
),
'upload_token'
:
generate_upload_token
(
g
.
user
),
'signature_token'
:
signature_token
(),
'access_token'
:
infrastructure
.
keycloak
.
access_token
...
...
nomad/api/upload.py
View file @
2b8429c2
...
...
@@ -319,7 +319,7 @@ class UploadListResource(Resource):
upload
.
process_upload
()
logger
.
info
(
'initiated processing'
)
if
bool
(
request
.
args
.
get
(
'
curl
'
,
False
)):
if
bool
(
request
.
args
.
get
(
'
token
'
,
False
)):
raise
DisableMarshalling
(
'''
Thanks for uploading your data to nomad.
...
...
nomad/datamodel/base.py
View file @
2b8429c2
...
...
@@ -27,39 +27,32 @@ class User:
# TODO legacy ids
"""
def
__init__
(
self
,
email
,
user_id
=
None
,
name
=
None
,
first_name
=
''
,
last_name
=
''
,
affiliation
=
None
,
created
:
datetime
.
datetime
=
None
,
token
=
None
,
**
kwargs
):
self
,
user_id
:
str
,
email
:
str
,
name
:
str
=
None
,
first_name
:
str
=
None
,
last_name
:
str
=
None
,
affiliation
:
str
=
None
,
affiliation_address
:
str
=
None
,
created
:
datetime
.
datetime
=
None
):
self
.
user_id
=
kwargs
.
get
(
'id'
,
kwargs
.
get
(
'sub'
,
user_id
))
self
.
email
=
email
assert
self
.
user_id
is
not
None
,
'Users must have a unique id'
assert
email
is
not
None
,
'Users must have an email'
assert
user_id
is
not
None
,
'Users must have a unique id'
self
.
first_name
=
kwargs
.
get
(
'given_name'
,
first_name
)
self
.
last_name
=
kwargs
.
get
(
'family_name'
,
last_name
)
name
=
kwargs
.
get
(
'username'
,
name
)
created_timestamp
=
kwargs
.
get
(
'createdTimestamp'
,
None
)
name
=
''
if
name
is
None
else
name
.
strip
()
self
.
first_name
=
''
if
first_name
is
None
else
first_name
.
strip
()
self
.
last_name
=
''
if
last_name
is
None
else
last_name
.
strip
()
self
.
user_id
=
user_id
self
.
email
=
email
if
len
(
self
.
last_name
)
>
0
and
len
(
self
.
first_name
)
>
0
:
self
.
name
=
'%s, %s'
%
(
self
.
last_name
,
self
.
first_name
)
self
.
name
=
'%s %s'
%
(
self
.
first_name
,
self
.
last_name
)
elif
len
(
name
)
!=
0
:
self
.
name
=
name
elif
len
(
self
.
last_name
)
!=
0
:
self
.
name
=
self
.
last_name
elif
len
(
self
.
first_name
)
!=
0
:
self
.
name
=
self
.
first_name
elif
name
is
not
None
:
self
.
name
=
name
else
:
self
.
name
=
'unnamed user'
if
created
is
not
None
:
self
.
created
=
None
elif
created_timestamp
is
not
None
:
self
.
created
=
datetime
.
datetime
.
fromtimestamp
(
created_timestamp
)
else
:
self
.
created
=
None
# TODO affliation
self
.
created
=
created
self
.
affiliation
=
affiliation
self
.
affiliation_address
=
affiliation_address
@
staticmethod
def
get
(
*
args
,
**
kwargs
)
->
'User'
:
...
...
nomad/infrastructure.py
View file @
2b8429c2
...
...
@@ -190,10 +190,10 @@ class Keycloak():
from
nomad
import
datamodel
g
.
user
=
datamodel
.
User
(
user_id
=
payload
.
get
(
'sub'
,
None
),
name
=
payload
.
get
(
'name'
,
None
),
email
=
payload
.
get
(
'email'
,
None
),
name
=
payload
.
get
(
'name'
,
None
),
first_name
=
payload
.
get
(
'given_name'
,
None
),
family
_name
=
payload
.
get
(
'family_name'
,
None
))
last
_name
=
payload
.
get
(
'family_name'
,
None
))
return
None
...
...
@@ -232,13 +232,15 @@ class Keycloak():
logger
.
error
(
'Could not retrieve user from keycloak'
,
exc_info
=
e
)
raise
e
kwargs
=
{
key
:
value
[
0
]
for
key
,
value
in
keycloak_user
.
get
(
'attributes'
,
{}).
items
()}
return
datamodel
.
User
(
user_id
=
keycloak_user
[
'id'
],
email
=
keycloak_user
[
'email'
],
name
=
keycloak_user
.
get
(
'username'
,
None
),
first_name
=
keycloak_user
.
get
(
'firstName'
,
None
),
family_name
=
keycloak_user
.
get
(
'lastName'
,
None
),
created
=
datetime
.
fromtimestamp
(
keycloak_user
[
'createdTimestamp'
]
/
1000
))
last_name
=
keycloak_user
.
get
(
'lastName'
,
None
),
created
=
datetime
.
fromtimestamp
(
keycloak_user
[
'createdTimestamp'
]
/
1000
),
**
kwargs
)
@
property
def
_admin_client
(
self
):
...
...
tests/conftest.py
View file @
2b8429c2
...
...
@@ -198,7 +198,7 @@ class KeycloakMock:
if
'Authorization'
in
request
.
headers
and
request
.
headers
[
'Authorization'
].
startswith
(
'Bearer '
):
user_id
=
request
.
headers
[
'Authorization'
].
split
(
None
,
1
)[
1
].
strip
()
g
.
oidc_access_token
=
user_id
return
User
(
**
test_users
[
user_id
])
g
.
user
=
User
(
**
test_users
[
user_id
])
def
get_user
(
self
,
user_id
=
None
,
email
=
None
):
if
user_id
is
not
None
:
...
...
tests/test_api.py
View file @
2b8429c2
...
...
@@ -26,7 +26,7 @@ import base64
from
nomad.api.app
import
rfc3339DateTime
from
nomad.api.auth
import
generate_upload_token
from
nomad
import
search
,
parsing
,
files
,
config
,
utils
from
nomad
import
search
,
parsing
,
files
,
config
,
utils
,
infrastructure
from
nomad.files
import
UploadFiles
,
PublicUploadFiles
from
nomad.processing
import
Upload
,
Calc
,
SUCCESS
from
nomad.datamodel
import
UploadWithMetadata
,
CalcWithMetadata
,
User
...
...
@@ -92,6 +92,16 @@ class TestKeycloak:
rv
=
client
.
get
(
'/auth/'
,
headers
=
auth_headers
)
assert
rv
.
status_code
==
200
def
test_get_user
(
self
,
keycloak
):
user
=
infrastructure
.
keycloak
.
get_user
(
email
=
'sheldon.cooper@nomad-coe.eu'
)
assert
user
.
email
is
not
None
assert
user
.
name
==
'Sheldon Cooper'
assert
user
.
first_name
==
'Sheldon'
assert
user
.
last_name
==
'Cooper'
assert
user
.
created
is
not
None
assert
user
.
affiliation
is
not
None
assert
user
.
affiliation_address
is
not
None
class
TestAuth
:
def
test_auth_wo_credentials
(
self
,
client
,
no_warn
):
...
...
@@ -104,11 +114,7 @@ class TestAuth:
self
.
assert_auth
(
client
,
json
.
loads
(
rv
.
data
))
def
assert_auth
(
self
,
client
,
auth
):
assert
'user'
in
auth
user
=
auth
[
'user'
]
for
key
in
[
'first_name'
,
'last_name'
,
'email'
,
'name'
,
'user_id'
]:
assert
key
in
user
assert
'user'
not
in
auth
assert
'access_token'
in
auth
assert
'upload_token'
in
auth
assert
'signature_token'
in
auth
...
...
@@ -241,12 +247,12 @@ class TestUploads:
rv
=
client
.
get
(
'/uploads/123456789012123456789012'
,
headers
=
test_user_auth
)
assert
rv
.
status_code
==
404
def
test_put_upload_token
(
self
,
client
,
non_empty_example_upload
,
test_user
,
no_warn
):
def
test_put_upload_token
(
self
,
client
,
non_empty_example_upload
,
test_user
):
url
=
'/uploads/?token=%s&local_path=%s&name=test_upload'
%
(
generate_upload_token
(
test_user
),
non_empty_example_upload
)
rv
=
client
.
put
(
url
)
assert
rv
.
status_code
==
200
self
.
assert
_upload
(
rv
.
data
,
name
=
'test_upload
'
)
assert
'Thanks for uploading'
in
rv
.
data
.
decode
(
'utf-8
'
)
@
pytest
.
mark
.
parametrize
(
'mode'
,
[
'multipart'
,
'stream'
,
'local_path'
])
@
pytest
.
mark
.
parametrize
(
'name'
,
[
None
,
'test_name'
])
...
...
@@ -692,7 +698,7 @@ class TestRepo():
(
1
,
'only_atoms'
,
[
'Br'
,
'K'
,
'Si'
]),
(
1
,
'only_atoms'
,
[
'Br'
,
'Si'
,
'K'
]),
(
1
,
'comment'
,
'specific'
),
(
1
,
'authors'
,
'Hofstadter
, Leonard
'
),
(
1
,
'authors'
,
'
Leonard
Hofstadter'
),
(
2
,
'files'
,
'test/mainfile.txt'
),
(
2
,
'paths'
,
'mainfile.txt'
),
(
2
,
'paths'
,
'test'
),
...
...
@@ -819,7 +825,7 @@ class TestRepo():
(
0
,
'system'
,
'atom'
),
(
1
,
'atoms'
,
'Br'
),
(
1
,
'atoms'
,
'Fe'
),
(
1
,
'authors'
,
'Hofstadter
, Leonard
'
),
(
1
,
'authors'
,
'
Leonard
Hofstadter'
),
(
2
,
'files'
,
'test/mainfile.txt'
),
(
0
,
'quantities'
,
'dos'
)
])
...
...
@@ -969,7 +975,7 @@ class TestRaw(UploadFilesBasedTests):
@
pytest
.
mark
.
parametrize
(
'query_params'
,
[
{
'atoms'
:
'Si'
},
{
'authors'
:
'
Cooper,
Sheldon'
}
{
'authors'
:
'Sheldon
Cooper
'
}
])
def
test_raw_files_from_query
(
self
,
client
,
processeds
,
test_user_auth
,
query_params
):
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment